import { useEffect, useState } from "react"; import { Link, useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { api } from "../api/client"; import type { Booking, Page } from "../api/types"; import { brusselsDayStartIso, brusselsNextDayStartIso, toBrusselsDateTimeLocal } from "../i18n/brusselsDateTime"; import { useLocaleFormat } from "../i18n/format"; import { StatusBadge } from "../components/Badge"; import { EmptyState, ErrorState, LoadingState, PageHeader } from "../components/PageChrome"; import { Pagination } from "../components/Pagination"; const STATUS_OPTIONS = ["reserved", "active", "returned", "cancelled", "blocked"]; /** Today's Brussels calendar day (YYYY-MM-DD) -- the operational "today", not the browser's. */ function brusselsToday(): string { return toBrusselsDateTimeLocal(new Date()).slice(0, 10); } export function Bookings() { const { t } = useTranslation("bookings"); const { formatShortDate } = useLocaleFormat(); const [searchParams, setSearchParams] = useSearchParams(); const [bookings, setBookings] = useState | null>(null); const [error, setError] = useState(null); const status = searchParams.get("status") ?? ""; const query = searchParams.get("q") ?? ""; const startsFrom = searchParams.get("from") ?? ""; const startsTo = searchParams.get("to") ?? ""; const location = searchParams.get("location") ?? ""; const sort = searchParams.get("sort") ?? "operational"; const page = Math.max(1, Number(searchParams.get("page") ?? "1") || 1); function updateFilters(updates: Record) { const next = new URLSearchParams(searchParams); Object.entries(updates).forEach(([key, value]) => { if (value === null || value === "") next.delete(key); else next.set(key, String(value)); }); setSearchParams(next, { replace: true }); } useEffect(() => { setBookings(null); setError(null); const params = new URLSearchParams({ page: String(page), page_size: "25" }); if (status) params.set("status", status); if (query) params.set("query", query); // Date filters are Brussels calendar days regardless of the browser's own time zone. if (startsFrom) params.set("starts_from", brusselsDayStartIso(startsFrom)); if (startsTo) params.set("starts_to", brusselsNextDayStartIso(startsTo)); if (location) params.set("location", location); params.set("sort", sort); const controller = new AbortController(); api .get>(`/api/v1/bookings?${params.toString()}`, { signal: controller.signal }) .then(setBookings) .catch(() => { if (!controller.signal.aborted) setError(t("list.unavailable")); }); return () => controller.abort(); }, [page, query, status, startsFrom, startsTo, location, sort, t]); return (
{t("list.create")}} />
{error && } {!error && !bookings && } {bookings && bookings.items.length === 0 && } {bookings && bookings.items.length > 0 &&
{t("list.count", { count: bookings.total })}{t("list.pageOf", { page: bookings.page, total: bookings.total_pages })}
{bookings.items.map((booking) => ( ))}
{t("list.title")}
{t("list.columns.reference")}{t("list.columns.customer")}{t("list.columns.vehicle")}{t("list.columns.window")}{t("list.columns.status")}
{booking.public_ref}{booking.public_ref} {booking.customer_name} {booking.vehicle_ref} {formatShortDate(booking.starts_at)} → {formatShortDate(booking.ends_at)}
updateFilters({ page: nextPage })} />
}
); }