fix(fuel): review fixes for dismiss button

- Disable all dismiss buttons while any is in flight so rapid
  clicks on a second vehicle don't silently no-op.
- Map API error slugs (bad_id, not_found, update_failed) to
  user-facing copy so window.alert shows prose, not machine tags.
- Add aria-busy to the dismiss buttons.
- Drop redundant e.stopPropagation(); preventDefault() is
  sufficient to suppress the wrapping <Link>.
This commit is contained in:
DEls 2026-08-28 12:52:29 +02:00
parent 1e212a7339
commit c633681789

View file

@ -184,6 +184,12 @@ function StatusBadge({ status, variancePct }: { status: FuelStatus; variancePct:
);
}
const DISMISS_ERROR_COPY: Record<string, string> = {
bad_id: "That vehicle id is malformed.",
not_found: "Vehicle not found.",
update_failed: "Server update failed. Please try again.",
};
export function FuelView({
rows,
vehicles,
@ -338,8 +344,16 @@ export function FuelView({
{ method: "POST" },
);
if (!res.ok) {
const data: { error?: string } = await res.json().catch(() => ({}));
throw new Error(data.error ?? `Dismiss failed (${res.status})`);
let slug: string | undefined;
try {
const data: { error?: string } = await res.json();
slug = data.error;
} catch {
/* non-JSON body — fall through */
}
const friendly =
(slug && DISMISS_ERROR_COPY[slug]) ?? `Dismiss failed (${res.status})`;
throw new Error(friendly);
}
// Re-pull server data so the overlay becomes a no-op and any
// other server-side state stays in sync.
@ -540,6 +554,7 @@ export function FuelView({
href={vehicleHref(v.id)}
onDismiss={handleDismiss}
busy={busyId === v.id}
disabled={busyId !== null}
/>
</li>
))}
@ -553,6 +568,7 @@ export function FuelView({
href={vehicleHref(v.id)}
onDismiss={handleDismiss}
busy={busyId === v.id}
disabled={busyId !== null}
/>
</li>
))}
@ -788,11 +804,13 @@ function AlertCard({
href,
onDismiss,
busy,
disabled,
}: {
v: VehicleSummary;
href: string;
onDismiss: (id: string) => void;
busy: boolean;
disabled: boolean;
}) {
const dominant = v.alert > 0 ? "alert" : "warn";
const toneClasses =
@ -867,10 +885,10 @@ function AlertCard({
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDismiss(v.id);
}}
disabled={busy}
disabled={disabled}
aria-busy={busy}
aria-label={`Mark ${v.description} as seen`}
className="inline-flex shrink-0 items-center gap-1 rounded-md border border-border/60 bg-card px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:border-[#F2B165]/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#F2B165] disabled:opacity-50"
>
@ -887,11 +905,13 @@ function AlertListRow({
href,
onDismiss,
busy,
disabled,
}: {
v: VehicleSummary;
href: string;
onDismiss: (id: string) => void;
busy: boolean;
disabled: boolean;
}) {
const dominant = v.alert > 0 ? "alert" : "warn";
const stripeColor =
@ -948,10 +968,10 @@ function AlertListRow({
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDismiss(v.id);
}}
disabled={busy}
disabled={disabled}
aria-busy={busy}
aria-label={`Mark ${v.description} as seen`}
className="ml-2 inline-flex shrink-0 items-center gap-1.5 rounded-md border border-border/60 bg-card px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:border-[#F2B165]/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#F2B165] disabled:opacity-50"
>