M1: implement operational core

Demo auth, seed import/reset, dashboard, vehicle/booking list+detail, audit trail. Backend: 19 tests passing, ruff clean. Frontend: React Router shell, typed API client, responsive pages. Verified end-to-end via curl and browser.
This commit is contained in:
NuklearRabbit
2026-08-01 21:20:53 +02:00
parent 04d26f1f2e
commit 03c5b60235
47 changed files with 2518 additions and 70 deletions
+80
View File
@@ -0,0 +1,80 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { api } from "../api/client";
import type { Booking } from "../api/types";
import { StatusBadge } from "../components/Badge";
const STATUS_OPTIONS = ["reserved", "active", "returned", "cancelled", "blocked"];
export function Bookings() {
const [bookings, setBookings] = useState<Booking[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [status, setStatus] = useState("");
useEffect(() => {
const params = new URLSearchParams();
if (status) params.set("status", status);
api
.get<Booking[]>(`/api/v1/bookings?${params.toString()}`)
.then(setBookings)
.catch(() => setError("Booking list is unavailable right now."));
}, [status]);
return (
<div className="page">
<h1>Bookings</h1>
<form className="filters" aria-label="Filter bookings">
<label>
Status
<select value={status} onChange={(e) => setStatus(e.target.value)}>
<option value="">All statuses</option>
{STATUS_OPTIONS.map((s) => (
<option key={s} value={s}>
{s}
</option>
))}
</select>
</label>
</form>
{error && <p className="error" role="alert">{error}</p>}
{!error && !bookings && <p>Loading bookings…</p>}
{bookings && bookings.length === 0 && <p>No bookings match these filters.</p>}
{bookings && bookings.length > 0 && (
<table className="data-table">
<caption className="visually-hidden">Bookings</caption>
<thead>
<tr>
<th scope="col">Reference</th>
<th scope="col">Customer</th>
<th scope="col">Vehicle</th>
<th scope="col">Window</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
{bookings.map((b) => (
<tr key={b.public_ref}>
<th scope="row">
<Link to={`/bookings/${b.public_ref}`}>{b.public_ref}</Link>
</th>
<td>{b.customer_name}</td>
<td>
<Link to={`/vehicles/${b.vehicle_ref}`}>{b.vehicle_ref}</Link>
</td>
<td>
{new Date(b.starts_at).toLocaleDateString("en-GB")} → {new Date(b.ends_at).toLocaleDateString("en-GB")}
</td>
<td>
<StatusBadge status={b.status} />
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}