M33: enforce booking readiness workflow

This commit is contained in:
NuklearRabbit
2026-08-10 22:23:11 +02:00
parent be33b46228
commit 82a933f6cd
12 changed files with 273 additions and 43 deletions
+33 -1
View File
@@ -27,6 +27,8 @@ export function BookingDetail() {
const [cancelling, setCancelling] = useState(false);
const [actionError, setActionError] = useState<ApiErrorInfo | null>(null);
const [checkoutResult, setCheckoutResult] = useState<CheckoutBookingResult | null>(null);
const [requirementsConfirmation, setRequirementsConfirmation] = useState("");
const [confirmingRequirements, setConfirmingRequirements] = useState(false);
const load = useCallback(() => {
if (!publicRef) return;
@@ -84,6 +86,24 @@ export function BookingDetail() {
load();
}
async function confirmRequirements(event: FormEvent) {
event.preventDefault();
if (!publicRef) return;
setConfirmingRequirements(true);
setActionError(null);
try {
const updated = await api.post<Booking>(`/api/v1/bookings/${publicRef}/complete-requirements`, {
confirmation: requirementsConfirmation,
});
setBooking(updated);
setRequirementsConfirmation("");
} catch (err) {
setActionError(describeApiError(t, err, "bookings:detail.requirementsFailed"));
} finally {
setConfirmingRequirements(false);
}
}
if (error) return <ErrorState message={error} />;
if (!booking) return <LoadingState label={t("detail.loading")} />;
@@ -101,6 +121,18 @@ export function BookingDetail() {
<div><dt>{t("detail.requirementsComplete")}</dt><dd>{booking.requirements_complete ? t("detail.yes") : t("detail.no")}</dd></div>
</dl></section>
{booking.status === "reserved" && !booking.requirements_complete ? (
<form className="record-surface requirements-confirmation" onSubmit={confirmRequirements}>
<div>
<h2>{t("detail.requirementsAction")}</h2>
<p>{t("detail.requirementsActionDetail")}</p>
</div>
<ApiErrorNotice error={actionError} />
<label>{t("detail.requirementsConfirmation")}<textarea required minLength={3} maxLength={500} value={requirementsConfirmation} onChange={(event) => setRequirementsConfirmation(event.target.value)} placeholder={t("detail.requirementsConfirmationPlaceholder")} /></label>
<div className="form-actions"><button className="button button-primary" type="submit" disabled={confirmingRequirements || requirementsConfirmation.trim().length < 3}>{confirmingRequirements ? t("detail.requirementsSaving") : t("detail.requirementsConfirm")}</button></div>
</form>
) : null}
{booking.status === "reserved" && <form className="record-surface booking-cancel-form" onSubmit={cancelBooking}>
<h2>{t("detail.cancelAction")}</h2>
<ApiErrorNotice error={actionError} />
@@ -109,7 +141,7 @@ export function BookingDetail() {
</form>}
{checkoutResult && <section className={`record-surface checkout-result ${checkoutResult.activated ? "success" : "warning"}`} role="status"><h2>{t(checkoutResult.activated ? "checkout.activatedTitle" : "checkout.blockedTitle")}</h2><p>{t(checkoutResult.activated ? "checkout.activatedDetail" : "checkout.blockedDetail", { inspection: checkoutResult.inspection_ref })}</p></section>}
{!checkoutResult && booking.status === "reserved" && <CheckoutForm bookingRef={booking.public_ref} vehicleRef={booking.vehicle_ref} onRecorded={handleCheckout} />}
{!checkoutResult && booking.status === "reserved" && booking.requirements_complete && <CheckoutForm bookingRef={booking.public_ref} vehicleRef={booking.vehicle_ref} onRecorded={handleCheckout} />}
{isReturnAnomalyScenario && !returnResult && canonicalOdometerKm !== null && (
<section className="record-surface scenario-callout" aria-label={t("returns:scenario.ariaLabel")}>