Yaltopia-Hotels/src/pages/BookingsPage.tsx
2026-03-22 03:55:51 +03:00

204 lines
7.0 KiB
TypeScript

import { useEffect, useState } from "react";
import { Link, useSearchParams } from "react-router-dom";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { apiGet } from "@/lib/api";
import type { Booking } from "@/lib/types";
import { formatDate, formatMoney } from "@/lib/format";
import { roomDisplayName } from "@/lib/room-utils";
export function BookingsPage() {
const [searchParams] = useSearchParams();
const ref = searchParams.get("referral") ?? "";
const [list, setList] = useState<Booking[]>([]);
const [status, setStatus] = useState<string>("all");
const [q, setQ] = useState("");
useEffect(() => {
const params = new URLSearchParams();
if (status !== "all") params.set("status", status);
if (q) params.set("q", q);
if (ref) params.set("referralCode", ref);
const t = setTimeout(() => {
apiGet<{ data: Booking[] }>(`/bookings?${params}`)
.then((r) => setList(r.data))
.catch(console.error);
}, 200);
return () => clearTimeout(t);
}, [status, q, ref]);
return (
<div className="space-y-6">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/dashboard">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Bookings</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-2xl font-bold">Bookings</h1>
<p className="text-muted-foreground text-sm">
Search, filter, export
</p>
</div>
<div className="flex flex-wrap gap-2">
<Button asChild>
<Link to="/bookings/new">+ New booking</Link>
</Button>
<Button variant="outline" asChild>
<a href="/api/export/bookings.csv" download>
Export CSV
</a>
</Button>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{["Total", "Confirmed", "Held", "Pending"].map((label, i) => (
<Card key={label} className="rounded-2xl">
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{label}
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">
{i === 0
? list.length
: list.filter((b) =>
label === "Confirmed"
? b.status === "confirmed"
: label === "Held"
? b.status === "held"
: b.status === "payment_pending"
).length}
</p>
</CardContent>
</Card>
))}
</div>
<Card className="rounded-2xl">
<CardContent className="pt-6">
<div className="mb-4 flex flex-col gap-3 sm:flex-row">
<Input
placeholder="Search guest, ref…"
value={q}
onChange={(e) => setQ(e.target.value)}
className="max-w-md"
/>
<Select value={status} onValueChange={setStatus}>
<SelectTrigger className="w-full sm:w-44">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All status</SelectItem>
<SelectItem value="confirmed">Confirmed</SelectItem>
<SelectItem value="held">Held</SelectItem>
<SelectItem value="payment_pending">Payment pending</SelectItem>
<SelectItem value="cancelled">Cancelled</SelectItem>
</SelectContent>
</Select>
</div>
<div className="hidden md:block overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead>Guest</TableHead>
<TableHead>Room</TableHead>
<TableHead>Dates</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Total</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{list.map((b) => (
<TableRow key={b.id}>
<TableCell>
<p className="font-medium">
{b.guest.firstName} {b.guest.lastName}
</p>
<p className="text-xs text-muted-foreground">
{b.guest.email}
</p>
</TableCell>
<TableCell className="text-sm">
{roomDisplayName(b.roomId)}
</TableCell>
<TableCell className="text-xs text-muted-foreground">
{formatDate(b.checkIn)} {formatDate(b.checkOut)}
</TableCell>
<TableCell>
<Badge variant="secondary">{b.status}</Badge>
</TableCell>
<TableCell className="text-right font-medium">
{formatMoney(b.pricing.total)}
</TableCell>
<TableCell>
<Button variant="outline" size="sm" asChild>
<Link to={`/bookings/${b.id}`}>Details</Link>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="space-y-3 md:hidden">
{list.map((b) => (
<Link
key={b.id}
to={`/bookings/${b.id}`}
className="block rounded-xl border p-4"
>
<p className="font-medium">
{b.guest.firstName} {b.guest.lastName}
</p>
<p className="text-xs text-muted-foreground">
{formatDate(b.checkIn)} · {b.status}
</p>
<p className="mt-2 font-semibold">
{formatMoney(b.pricing.total)}
</p>
</Link>
))}
</div>
</CardContent>
</Card>
</div>
);
}