feat(ui): redesign fleet and booking workflows

This commit is contained in:
NuklearRabbit
2026-08-02 03:27:13 +02:00
parent 6f0054c878
commit 3d78cac850
4 changed files with 68 additions and 49 deletions
+24 -15
View File
@@ -3,6 +3,7 @@ import { Link } from "react-router-dom";
import { api } from "../api/client";
import type { Vehicle } from "../api/types";
import { StatusBadge } from "../components/Badge";
import { EmptyState, ErrorState, LoadingState, PageHeader } from "../components/PageChrome";
const STATUS_OPTIONS = ["available", "rented", "cleaning", "maintenance", "blocked"];
@@ -11,8 +12,11 @@ export function Vehicles() {
const [error, setError] = useState<string | null>(null);
const [status, setStatus] = useState("");
const [attentionOnly, setAttentionOnly] = useState(false);
const [query, setQuery] = useState("");
useEffect(() => {
setVehicles(null);
setError(null);
const params = new URLSearchParams();
if (status) params.set("status", status);
if (attentionOnly) params.set("attention_only", "true");
@@ -24,9 +28,13 @@ export function Vehicles() {
return (
<div className="page">
<h1>Vehicles</h1>
<PageHeader eyebrow="Fleet / Registry" title="Vehicle fleet" description="Live operational state, location and service readiness." />
<form className="filters" aria-label="Filter vehicles">
<label>
Search
<input type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Reference, make or location" />
</label>
<label>
Status
<select value={status} onChange={(e) => setStatus(e.target.value)}>
@@ -48,12 +56,13 @@ export function Vehicles() {
</label>
</form>
{error && <p className="error" role="alert">{error}</p>}
{!error && !vehicles && <p>Loading vehicles…</p>}
{vehicles && vehicles.length === 0 && <p>No vehicles match these filters.</p>}
{error && <ErrorState message={error} />}
{!error && !vehicles && <LoadingState label="Loading fleet registry…" />}
{vehicles && vehicles.length === 0 && <EmptyState icon="fleet" title="No vehicles found" detail="Adjust the current fleet filters." />}
{vehicles && vehicles.length > 0 && (
<table className="data-table">
{vehicles && vehicles.length > 0 && (() => {
const filtered = vehicles.filter((v) => `${v.public_ref} ${v.make} ${v.model} ${v.location}`.toLowerCase().includes(query.toLowerCase()));
return filtered.length === 0 ? <EmptyState icon="search" title="No matching vehicles" detail="Try a broader search term." /> : <div className="table-shell"><div className="table-meta"><span>{filtered.length} vehicles</span><span>Persisted fleet data</span></div><table className="data-table">
<caption className="visually-hidden">Vehicle fleet</caption>
<thead>
<tr>
@@ -67,24 +76,24 @@ export function Vehicles() {
</thead>
<tbody>
{vehicles.map((v) => (
<tr key={v.public_ref}>
<th scope="row">
<tr key={v.public_ref} className={v.attention ? "row-attention" : ""}>
<th scope="row" data-label="Reference">
<Link to={`/vehicles/${v.public_ref}`}>{v.public_ref}</Link>
</th>
<td>
<td data-label="Make / model">
{v.make} {v.model} ({v.model_year})
</td>
<td>{v.location}</td>
<td>
<td data-label="Location">{v.location}</td>
<td data-label="Status">
<StatusBadge status={v.operational_status} />
</td>
<td>{v.odometer_km.toLocaleString("en-GB")}</td>
<td>{v.attention ? "Needs attention" : "—"}</td>
<td data-label="Odometer">{v.odometer_km.toLocaleString("en-GB")}</td>
<td data-label="Attention">{v.attention ? <span className="attention-flag">Needs attention</span> : "—"}</td>
</tr>
))}
</tbody>
</table>
)}
</table></div>;
})()}
</div>
);
}