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

140 lines
4.5 KiB
TypeScript

import { format } from "date-fns";
import { useEffect, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { apiGet } from "@/lib/api";
import type { Room } from "@/lib/types";
interface TimelineResp {
days: string[];
rooms: Room[];
segments: {
bookingId: string;
guestName: string;
roomId: string;
start: string;
end: string;
status: string;
paymentLabel: string;
source: string;
}[];
}
export function ReservationsPage() {
const [month, setMonth] = useState(format(new Date(), "yyyy-MM"));
const [data, setData] = useState<TimelineResp | null>(null);
useEffect(() => {
apiGet<TimelineResp>(`/reservations/timeline?month=${month}`)
.then(setData)
.catch(console.error);
}, [month]);
if (!data)
return <p className="text-muted-foreground">Loading timeline</p>;
const dayWidth = 56;
const roomCol = 120;
return (
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 className="text-2xl font-bold">Reservations</h1>
<p className="text-sm text-muted-foreground">
Gantt-style view (mock data)
</p>
</div>
<input
type="month"
value={month}
onChange={(e) => setMonth(e.target.value)}
className="rounded-xl border border-input bg-background px-3 py-2 text-sm"
/>
</div>
<div className="flex flex-wrap gap-2">
<Badge>Occupied</Badge>
<Badge variant="secondary">Check-in / out</Badge>
<Badge variant="outline">Reserved</Badge>
</div>
<Card className="rounded-2xl">
<CardHeader>
<CardTitle className="text-base">Timeline</CardTitle>
</CardHeader>
<CardContent className="overflow-x-auto">
<div
className="relative min-w-max"
style={{
width: roomCol + data.days.length * dayWidth,
}}
>
<div className="flex border-b">
<div
className="shrink-0 border-r p-2 text-xs font-medium"
style={{ width: roomCol }}
>
Room
</div>
{data.days.map((d) => (
<div
key={d}
className="shrink-0 border-r p-1 text-center text-[10px] text-muted-foreground"
style={{ width: dayWidth }}
>
{d.slice(8)}
</div>
))}
</div>
{data.rooms.map((room) => (
<div key={room.id} className="flex border-b">
<div
className="shrink-0 border-r p-2 text-xs font-medium"
style={{ width: roomCol }}
>
{room.name}
</div>
<div
className="relative shrink-0"
style={{ width: data.days.length * dayWidth, height: 48 }}
>
{data.segments
.filter((s) => s.roomId === room.id)
.map((s) => {
const startIdx = data.days.findIndex(
(d) => d >= s.start
);
const endIdx = data.days.findIndex((d) => d >= s.end);
const si =
startIdx >= 0 ? startIdx : 0;
const ei =
endIdx >= 0 ? endIdx : data.days.length;
const span = Math.max(1, ei - si);
return (
<div
key={s.bookingId}
className="absolute top-2 flex h-8 items-center rounded-lg border bg-accent px-2 text-[10px] shadow-sm"
style={{
left: si * dayWidth + 4,
width: span * dayWidth - 8,
}}
title={`${s.guestName} · ${s.paymentLabel}`}
>
<span className="truncate font-medium">
{s.guestName}
</span>
</div>
);
})}
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
);
}