feat(ui): redesign return and data-quality experiences

This commit is contained in:
NuklearRabbit
2026-08-02 03:27:17 +02:00
parent 3d78cac850
commit ce98a5f1c9
3 changed files with 61 additions and 40 deletions
+34 -15
View File
@@ -2,6 +2,8 @@ import { useState, type FormEvent } from "react";
import { Link } from "react-router-dom";
import { api, ApiError } from "../api/client";
import type { RegisterReturnRequest, RegisterReturnResult } from "../api/types";
import { Icon } from "./Icons";
import { StatusBadge } from "./Badge";
function newIdempotencyKey(): string {
return typeof crypto.randomUUID === "function"
@@ -11,11 +13,11 @@ function newIdempotencyKey(): string {
export function ReturnResultPanel({ result }: { result: RegisterReturnResult }) {
return (
<section className="panel return-result" aria-labelledby="return-result-heading">
<h2 id="return-result-heading">Return registered</h2>
<section className="panel return-result success-panel" aria-labelledby="return-result-heading" aria-live="polite">
<div className="result-heading"><span><Icon name="check" /></span><div><p className="page-eyebrow">Committed locally</p><h2 id="return-result-heading">Return registered</h2></div></div>
<dl className="detail-grid">
<div><dt>Inspection</dt><dd>{result.inspection_ref}</dd></div>
<div><dt>Resulting vehicle status</dt><dd>{result.resulting_vehicle_status}</dd></div>
<div><dt>Resulting vehicle status</dt><dd><StatusBadge status={result.resulting_vehicle_status} /></dd></div>
<div>
<dt>Quality issue</dt>
<dd>{result.quality_issue_ref ?? "None created"}</dd>
@@ -38,7 +40,7 @@ export function ReturnResultPanel({ result }: { result: RegisterReturnResult })
</p>
)}
<p>
<Link to={`/vehicles/${result.vehicle_ref}`}>View vehicle {result.vehicle_ref}</Link>
<Link className="button button-secondary" to={`/vehicles/${result.vehicle_ref}`}>View vehicle {result.vehicle_ref}<Icon name="chevron" /></Link>
</p>
</section>
);
@@ -60,9 +62,14 @@ export function ReturnForm({
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [idempotencyKey] = useState(newIdempotencyKey);
const [step, setStep] = useState<"capture" | "review">("capture");
async function handleSubmit(e: FormEvent) {
e.preventDefault();
if (step === "capture") {
setStep("review");
return;
}
setError(null);
setSubmitting(true);
try {
@@ -93,10 +100,12 @@ export function ReturnForm({
return (
<form className="panel return-form" onSubmit={handleSubmit} aria-labelledby="return-form-heading">
<h2 id="return-form-heading">Register return</h2>
<div className="return-progress" aria-label="Return registration progress"><span className="is-complete"><i>1</i> Capture</span><b /><span className={step === "review" ? "is-active" : ""}><i>2</i> Review</span><b /><span><i>3</i> Result</span></div>
<div className="section-heading"><div><p className="page-eyebrow">Booking {bookingRef}</p><h2 id="return-form-heading">{step === "capture" ? "Register vehicle return" : "Review return impact"}</h2><p>{step === "capture" ? "Record the hand-back condition. Operational consequences are calculated on commit." : "Confirm the inspection facts before they update fleet state and queue automation."}</p></div></div>
{error && <p className="error" role="alert">{error}</p>}
<label>
{step === "capture" ? <div className="return-capture">
<div className="form-grid"><label>
End odometer (km)
<input
type="number"
@@ -117,9 +126,9 @@ export function ReturnForm({
value={fuel}
onChange={(e) => setFuel(e.target.value)}
/>
</label>
</label></div>
<label className="checkbox-label">
<fieldset className="condition-fieldset"><legend>Vehicle condition</legend><label className="checkbox-label check-card">
<input
type="checkbox"
checked={cleanlinessOk}
@@ -128,7 +137,7 @@ export function ReturnForm({
Cleanliness acceptable
</label>
<label className="checkbox-label">
<label className="checkbox-label check-card">
<input
type="checkbox"
checked={damageReported}
@@ -137,23 +146,33 @@ export function ReturnForm({
Damage reported
</label>
<label className="checkbox-label">
<label className="checkbox-label check-card">
<input
type="checkbox"
checked={technicalWarning}
onChange={(e) => setTechnicalWarning(e.target.checked)}
/>
Technical warning
</label>
</label></fieldset>
<label>
Notes
<textarea value={notes} onChange={(e) => setNotes(e.target.value)} maxLength={2000} rows={3} />
</label>
</label></div> : <div className="return-review" aria-live="polite">
<dl className="review-facts"><div><dt>Odometer</dt><dd>{Number(odometer).toLocaleString("en-GB")} km</dd></div><div><dt>Fuel</dt><dd>{fuel}%</dd></div><div><dt>Cleanliness</dt><dd>{cleanlinessOk ? "Accepted" : "Follow-up needed"}</dd></div><div><dt>Damage</dt><dd>{damageReported ? "Reported" : "None reported"}</dd></div><div><dt>Technical warning</dt><dd>{technicalWarning ? "Reported" : "None reported"}</dd></div></dl>
<div className={`impact-preview ${damageReported || technicalWarning || !cleanlinessOk ? "impact-warning" : "impact-ready"}`}>
<Icon name={damageReported || technicalWarning || !cleanlinessOk ? "alert" : "check"} />
<div><strong>Expected fleet state</strong><p>{damageReported || technicalWarning ? "The vehicle will move to maintenance and may put its next booking at risk." : !cleanlinessOk ? "The vehicle will move to cleaning before it becomes available." : "The vehicle is expected to become available after the return is committed."}</p></div>
</div>
<ul className="commit-list"><li><Icon name="check" /> Create a return inspection</li><li><Icon name="check" /> Update the booking and vehicle atomically</li><li><Icon name="check" /> Queue n8n delivery after the local commit</li></ul>
</div>}
<button type="submit" disabled={submitting}>
{submitting ? "Registering…" : "Register return"}
</button>
<div className="form-actions">
{step === "review" && <button className="button button-secondary" type="button" onClick={() => setStep("capture")} disabled={submitting}><Icon name="arrow-left" /> Edit details</button>}
<button className="button button-primary" type="submit" disabled={submitting}>
{submitting ? "Registering…" : step === "capture" ? "Review return" : "Confirm return"} {step === "capture" && <Icon name="chevron" />}
</button>
</div>
</form>
);
}