feat(ui): redesign fleet and booking workflows
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user