M33: enforce booking readiness workflow
This commit is contained in:
@@ -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")}>
|
||||
|
||||
Reference in New Issue
Block a user