import { useEffect, useState } from "react"; import { Link, useSearchParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { api } from "../api/client"; import type { Page, Vehicle } from "../api/types"; 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 = ["available", "rented", "cleaning", "maintenance", "blocked"]; export function Vehicles() { const { t } = useTranslation("fleet"); const { formatDateTime, formatNumber } = useLocaleFormat(); const [searchParams, setSearchParams] = useSearchParams(); const [vehicles, setVehicles] = useState | null>(null); const [error, setError] = useState(null); const status = searchParams.get("status") ?? ""; const attentionOnly = searchParams.get("attention_only") === "true"; const query = searchParams.get("q") ?? ""; const location = searchParams.get("location") ?? ""; 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 === "" || value === false) next.delete(key); else next.set(key, String(value)); }); setSearchParams(next, { replace: true }); } useEffect(() => { setVehicles(null); setError(null); const params = new URLSearchParams(); if (status) params.set("status", status); if (attentionOnly) params.set("attention_only", "true"); if (query) params.set("query", query); if (location) params.set("location", location); params.set("page", String(page)); params.set("page_size", "25"); const controller = new AbortController(); api .get>(`/api/v1/vehicles?${params.toString()}`, { signal: controller.signal }) .then(setVehicles) .catch(() => { if (!controller.signal.aborted) setError(t("list.unavailable")); }); return () => controller.abort(); }, [status, attentionOnly, query, location, page, t]); return (
{error && } {!error && !vehicles && } {vehicles && vehicles.items.length === 0 && } {vehicles && vehicles.items.length > 0 &&
{t("list.count", { count: vehicles.total })}{t("list.persisted")}
{vehicles.items.map((v) => ( ))}
{t("list.title")}
{t("list.columns.reference")} {t("list.columns.makeModel")} {t("list.columns.location")} {t("list.columns.status")} {t("list.columns.odometer")} {t("list.columns.service")} {t("list.columns.nextBooking")} {t("list.columns.attention")}
{v.public_ref} {v.public_ref} {v.make} {v.model} ({v.model_year}) {v.location} {formatNumber(v.odometer_km)} {v.service_remaining_km <= 0 ? t("list.serviceDue") : t("list.serviceRemaining", { count: formatNumber(v.service_remaining_km) })} {v.next_booking_ref && v.next_booking_at ? {v.next_booking_ref}{formatDateTime(v.next_booking_at)} : "—"} {v.attention && v.attention_reason ? {t(`list.attentionReasons.${v.attention_reason}`)} : "—"}
updateFilters({ page: nextPage })} />
}
); }