update joins in repository services to use entity classes and enhance booking form with hazardous/reefer toggles

This commit is contained in:
Marshal
2026-07-04 04:22:03 +00:00
parent 8ea2c8e95a
commit 3e94aa08f1

View File

@@ -26,6 +26,70 @@ type BookingForm = UseFormReturn<
BookingFormValues
>;
/**
* One numbered toggle per container unit in the line — tap units to mark how
* many are hazardous/refrigerated (2 hazardous → toggle 2 units on). Selection
* fills from unit 1: tapping unit N selects 1..N, tapping a selected unit N
* keeps 1..N-1 — the count is always derived, never free-typed, so it can't
* exceed the line quantity.
*/
function UnitCountToggles({
total,
value,
onChange,
label,
activeBg,
activeBorder,
activeColor,
}: {
total: number;
value: string;
onChange: (v: string) => void;
label: string;
activeBg: string;
activeBorder: string;
activeColor: string;
}) {
const count = Math.min(total, Math.max(0, Math.floor(Number(value) || 0)));
return (
<div>
<Text fz={12} fw={600} c="#4A5A68" mb={6}>
{label} · {count}/{total} selected
</Text>
<div className="flex flex-wrap gap-2">
{Array.from({ length: total }, (_, i) => {
const selected = i < count;
return (
<button
key={i}
type="button"
aria-pressed={selected}
aria-label={`Container ${i + 1}`}
onClick={() => onChange(String(selected ? i : i + 1))}
className="rounded-lg"
style={{
minWidth: 40,
padding: "6px 10px",
fontSize: 12,
fontWeight: 700,
cursor: "pointer",
border: `1.5px solid ${selected ? activeBorder : "#E6ECF2"}`,
background: selected ? activeBg : "#fff",
color: selected ? activeColor : "#6B7C8E",
transition:
"background 120ms ease, border-color 120ms ease, color 120ms ease",
}}
>
#{i + 1}
</button>
);
})}
</div>
</div>
);
}
export function Step5CargoDetails({
form,
referenceData,
@@ -144,16 +208,6 @@ export function Step5CargoDetails({
const lineQtyOf = (index: number) =>
Math.max(1, Number(form.getValues(`containers.${index}.qty`) ?? 1) || 1);
const lineMax = (index: number) => lineQtyOf(index);
// When a flag is switched on, default its count to the whole line.
const defaultLineQty = (index: number) => lineQtyOf(index).toString();
// Clamp a typed value into 1..lineQty (empty stays empty so the field can be
// cleared; the schema flags an empty value as required while the switch is on).
const clampToLine = (raw: string, index: number) => {
if (raw === "") return "";
const n = Number(raw);
if (Number.isNaN(n)) return raw;
return Math.min(lineQtyOf(index), Math.max(1, Math.floor(n))).toString();
};
// After the line quantity changes, pull any active count back within bounds.
const clampDependentQty = (index: number, newLineQty: number) => {
const max = Math.max(1, newLineQty);
@@ -636,7 +690,7 @@ export function Step5CargoDetails({
hazField.onChange(v);
form.setValue(
`containers.${index}.hazardousQty`,
v ? defaultLineQty(index) : "0",
v ? "1" : "0",
{ shouldDirty: true, shouldValidate: true },
);
}}
@@ -645,22 +699,22 @@ export function Step5CargoDetails({
name={`containers.${index}.hazardousQty`}
control={form.control}
render={({ field: hq, fieldState }) => (
<TextInput
type="number"
size="sm"
label="How many hazardous?"
min={1}
max={lineMax(index)}
value={hq.value ?? ""}
onChange={(e) =>
hq.onChange(
clampToLine(e.currentTarget.value, index),
)
}
onBlur={hq.onBlur}
error={fieldState.error?.message}
radius="md"
/>
<div>
<UnitCountToggles
total={lineMax(index)}
value={hq.value ?? "0"}
onChange={hq.onChange}
label="Tap the hazardous containers"
activeBg="#FBEAE7"
activeBorder="#E4A69B"
activeColor="#C0392B"
/>
{fieldState.error?.message ? (
<Text fz={11} c="red.7" mt={4}>
{fieldState.error.message}
</Text>
) : null}
</div>
)}
/>
</ToggleRow>
@@ -681,7 +735,7 @@ export function Step5CargoDetails({
reeField.onChange(v);
form.setValue(
`containers.${index}.reeferQty`,
v ? defaultLineQty(index) : "0",
v ? "1" : "0",
{ shouldDirty: true, shouldValidate: true },
);
}}
@@ -690,22 +744,22 @@ export function Step5CargoDetails({
name={`containers.${index}.reeferQty`}
control={form.control}
render={({ field: rq, fieldState }) => (
<TextInput
type="number"
size="sm"
label="How many refrigerated?"
min={1}
max={lineMax(index)}
value={rq.value ?? ""}
onChange={(e) =>
rq.onChange(
clampToLine(e.currentTarget.value, index),
)
}
onBlur={rq.onBlur}
error={fieldState.error?.message}
radius="md"
/>
<div>
<UnitCountToggles
total={lineMax(index)}
value={rq.value ?? "0"}
onChange={rq.onChange}
label="Tap the refrigerated containers"
activeBg="#E9F0F8"
activeBorder="#A9C2E0"
activeColor="#2E5B96"
/>
{fieldState.error?.message ? (
<Text fz={11} c="red.7" mt={4}>
{fieldState.error.message}
</Text>
) : null}
</div>
)}
/>
</ToggleRow>