Improve book-in form field clarity

Restyle booking fields as high-contrast cards, add visible required/optional indicators and ARIA requirements, and make lookup and form errors more prominent.
This commit is contained in:
Dave 2026-11-09 16:07:42 +02:00
parent 3113830a42
commit 694c2a2292

View file

@ -16,6 +16,28 @@ import type { Customer, Supplier } from "@/lib/types";
/** Ten photos is plenty for one booking. */
const MAX_PHOTOS = 10;
/** Fields stand out from their card: white well, firm border. */
const FIELD =
"min-h-[56px] border-2 border-foreground/30 bg-background text-base shadow-sm focus-visible:border-primary";
const AREA =
"border-2 border-foreground/30 bg-background text-base shadow-sm focus-visible:border-primary";
const PICKER =
"min-h-[56px] w-full border-2 border-foreground/30 bg-background text-base shadow-sm";
const CARD = "space-y-2 rounded-2xl border border-foreground/15 bg-muted/60 p-4 shadow-sm";
/** Says out loud whether a field has to be filled in. */
function Tag({ required }: { required?: boolean }) {
return required ? (
<span className="rounded-full bg-destructive/10 px-2 py-0.5 text-xs font-semibold text-destructive">
Required
</span>
) : (
<span className="rounded-full bg-foreground/10 px-2 py-0.5 text-xs font-medium text-muted-foreground">
Optional
</span>
);
}
type Lookups = { customers: Customer[]; suppliers: Supplier[] };
/**
@ -239,8 +261,11 @@ export function BookInForm() {
Photos first: the machine is in front of you when you start, and
the photo is the one thing the form will not book in without.
*/}
<div className="space-y-2">
<Label htmlFor="photos">Photos of the machine</Label>
<div className={CARD}>
<div className="flex items-center justify-between gap-2">
<Label htmlFor="photos">Photos of the machine</Label>
<Tag required />
</div>
<p className="text-sm text-muted-foreground">
Take a photo of the machine as it arrived.
</p>
@ -257,7 +282,7 @@ export function BookInForm() {
<Button
type="button"
variant="outline"
className="min-h-[56px] w-full text-base"
className={PICKER}
disabled={photos.length >= MAX_PHOTOS}
onClick={() => fileRef.current?.click()}
>
@ -280,11 +305,14 @@ export function BookInForm() {
"Whose machine is it?" reachable as a label for the picker.
*/}
<div
className="space-y-2"
className={CARD}
role="group"
aria-labelledby="customer-label"
>
<Label id="customer-label">Whose machine is it?</Label>
<div className="flex items-center justify-between gap-2">
<Label id="customer-label">Whose machine is it?</Label>
<Tag required />
</div>
{lookupsLoaded ? (
<CustomerSearch
value={customerId}
@ -296,7 +324,7 @@ export function BookInForm() {
<Button
type="button"
variant="outline"
className="min-h-[56px] w-full text-base"
className={PICKER}
disabled={lookupsLoading}
onClick={() => void loadLookups("customer")}
>
@ -304,65 +332,85 @@ export function BookInForm() {
</Button>
)}
{lookupsError?.which === "customer" ? (
<p className="rounded-md border border-destructive/40 p-3 text-sm">
<p className="rounded-md border-2 border-destructive bg-destructive/10 p-3 text-sm font-medium text-destructive">
{lookupsError.message}
</p>
) : null}
</div>
<div className="space-y-2">
<Label htmlFor="machine">What is the machine?</Label>
<div className={CARD}>
<div className="flex items-center justify-between gap-2">
<Label htmlFor="machine">What is the machine?</Label>
<Tag required />
</div>
<Input
id="machine"
aria-required="true"
value={machine}
onChange={(e) => setMachine(e.target.value)}
placeholder="e.g. Welding machine"
className="min-h-[56px] text-base"
className={FIELD}
/>
</div>
<div className="space-y-2">
<Label htmlFor="serial_no">Serial number</Label>
<div className={CARD}>
<div className="flex items-center justify-between gap-2">
<Label htmlFor="serial_no">Serial number</Label>
<Tag required />
</div>
<Input
id="serial_no"
aria-required="true"
value={serialNo}
onChange={(e) => setSerialNo(e.target.value)}
placeholder="e.g. WM-9"
className="min-h-[56px] text-base"
className={FIELD}
/>
</div>
<div className="space-y-2">
<Label htmlFor="fault">What does the customer say is wrong?</Label>
<div className={CARD}>
<div className="flex items-center justify-between gap-2">
<Label htmlFor="fault">What does the customer say is wrong?</Label>
<Tag required />
</div>
<Textarea
id="fault"
aria-required="true"
value={fault}
onChange={(e) => setFault(e.target.value)}
placeholder="e.g. Will not strike"
rows={3}
className={AREA}
/>
</div>
<div className="space-y-2">
<Label htmlFor="supplier_scope">What must the supplier look at?</Label>
<div className={CARD}>
<div className="flex items-center justify-between gap-2">
<Label htmlFor="supplier_scope">What must the supplier look at?</Label>
<Tag />
</div>
<Textarea
id="supplier_scope"
value={supplierScope}
onChange={(e) => setSupplierScope(e.target.value)}
placeholder="e.g. Strip and quote the strike mechanism"
rows={3}
className={AREA}
/>
<p className="text-sm text-muted-foreground">
Optional. Leave it empty if you are not sure yet.
Leave it empty if you are not sure yet.
</p>
</div>
<div
className="space-y-2"
className={CARD}
role="group"
aria-labelledby="supplier-label"
>
<Label id="supplier-label">Which supplier?</Label>
<div className="flex items-center justify-between gap-2">
<Label id="supplier-label">Which supplier?</Label>
<Tag />
</div>
{lookupsLoaded ? (
<SupplierSearch
value={supplierId}
@ -374,7 +422,7 @@ export function BookInForm() {
<Button
type="button"
variant="outline"
className="min-h-[56px] w-full text-base"
className={PICKER}
disabled={lookupsLoading}
onClick={() => void loadLookups("supplier")}
>
@ -382,19 +430,19 @@ export function BookInForm() {
</Button>
)}
{lookupsError?.which === "supplier" ? (
<p className="rounded-md border border-destructive/40 p-3 text-sm">
<p className="rounded-md border-2 border-destructive bg-destructive/10 p-3 text-sm font-medium text-destructive">
{lookupsError.message}
</p>
) : null}
<p className="text-sm text-muted-foreground">
Optional — the office can pick the supplier later.
The office can pick the supplier later.
</p>
</div>
{error ? (
<p
role="alert"
className="rounded-md border border-destructive/40 p-3 text-sm"
className="rounded-md border-2 border-destructive bg-destructive/10 p-3 text-sm font-medium text-destructive"
>
{error}
</p>
@ -404,7 +452,7 @@ export function BookInForm() {
<Button
type="button"
variant="outline"
className="min-h-[56px] w-full text-base"
className={PICKER}
onClick={() => router.push("/repairs-app/" + createdRepairId)}
>
Open the repair