feat(customers): lift active/inactive toggle to top of customer form
The active/inactive customer switch was buried under the
'Details' section after the sales rep and description — easy
to miss when the only thing the operator wants to do is mark
a customer as inactive.
Moved it to the second row of the form, immediately after
the customer name field, where it's the first decision the
operator makes. Visual treatment bumped to match the new
prominence: a green-tinted container when active, muted
grey when inactive, so the state is readable from across
the room instead of requiring the operator to read the
trigger label.
state semantics (unchanged from bbfa888):
- 'status' is a PB bool, true = active, false = inactive
- new customers default to true via the form's
'status: customer?.status ?? true' initializer
- legacy rows with status undefined render as Active on
both the detail and list pages (same default-active
contract on read) — no migration needed
- the only way to render an inactive customer is to flip
the switch and save it; PB then stores false
This commit is contained in:
parent
da90706ce0
commit
1bc0da40e7
1 changed files with 69 additions and 35 deletions
|
|
@ -24,6 +24,7 @@ import {
|
|||
CheckCircle2,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import getPocketBase from "@/lib/pocketbase";
|
||||
import { getAllCompanies, findCustomerDuplicates } from "@/lib/pb-helpers";
|
||||
import { CompanySearch } from "@/components/ui/company-search";
|
||||
|
|
@ -384,6 +385,68 @@ export function CustomerForm({ open, onOpenChange, customer, onSaved }: Customer
|
|||
/>
|
||||
<DuplicateWarning matches={duplicateMatches} entityLabel="Customer" />
|
||||
</div>
|
||||
{/*
|
||||
Active/Inactive customer toggle — lifted to the top of the
|
||||
form (2026-09) so the operator can flip the flag without
|
||||
scrolling past company / sales rep / description. State
|
||||
semantics: `status` is a PB bool, true = active, false =
|
||||
inactive; legacy rows that pre-date the field come back
|
||||
as `undefined` and collapse to `true` via `?? true` in
|
||||
`buildCustomerFormState`, so untouched existing customers
|
||||
read as Active on first edit.
|
||||
|
||||
Visual: the switch has a coloured background that flips
|
||||
with state — green when active, muted when inactive — so
|
||||
the answer is one glance from across the room, not "did
|
||||
I read the trigger label right?". The icon (Check /
|
||||
X-circle) reinforces the same answer for screen-reader
|
||||
users.
|
||||
*/}
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between gap-4 rounded-md border px-3 py-3 transition-colors",
|
||||
form.status
|
||||
? "border-success/40 bg-success/10"
|
||||
: "border-muted-foreground/30 bg-muted/40"
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0 flex items-start gap-2.5">
|
||||
{form.status ? (
|
||||
<CheckCircle2
|
||||
className="h-4 w-4 text-success shrink-0 mt-0.5"
|
||||
aria-hidden
|
||||
/>
|
||||
) : (
|
||||
<XCircle
|
||||
className="h-4 w-4 text-muted-foreground shrink-0 mt-0.5"
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
<div className="min-w-0">
|
||||
<Label
|
||||
htmlFor="customer-status"
|
||||
className="text-sm font-medium leading-none"
|
||||
>
|
||||
{form.status ? "Active customer" : "Inactive customer"}
|
||||
</Label>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">
|
||||
New customers are active by default. Flip this
|
||||
off when the customer stops working for the
|
||||
mine — historical quotes and orders stay
|
||||
attached, but the customer drops off the
|
||||
active pickers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id="customer-status"
|
||||
checked={!!form.status}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm({ ...form, status: checked === true })
|
||||
}
|
||||
aria-label={form.status ? "Active customer" : "Inactive customer"}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldLabel icon={Building2} label="Company" />
|
||||
<CompanySearch
|
||||
|
|
@ -542,42 +605,13 @@ export function CustomerForm({ open, onOpenChange, customer, onSaved }: Customer
|
|||
/>
|
||||
</div>
|
||||
{/*
|
||||
Active-customer toggle (added 2026-09). PB `status` is a
|
||||
bool: true = active, false = inactive (e.g. the mine
|
||||
stopped trading, customer moved to a different mine).
|
||||
Existing customers the operator creates without touching
|
||||
this row will land as `true` — see `?? true` in
|
||||
`buildCustomerFormState`. The icon flips with state so
|
||||
the meaning is clear even without the label colour.
|
||||
Active/Inactive toggle used to live here, but it was
|
||||
buried past company / rep / description and easy to
|
||||
miss. It's now the second row of the form (right
|
||||
under the customer name) so flipping the flag is one
|
||||
tap. Keeping the Save layer in `performSave` unchanged
|
||||
— `status: !!form.status` already shipped 2026-09.
|
||||
*/}
|
||||
<div className="flex items-start justify-between gap-4 rounded-md border border-input/40 px-3 py-2.5">
|
||||
<div className="min-w-0">
|
||||
<Label
|
||||
htmlFor="customer-status"
|
||||
className="flex items-center gap-1.5 text-sm font-medium"
|
||||
>
|
||||
{form.status ? (
|
||||
<CheckCircle2 className="h-3.5 w-3.5 text-success" aria-hidden />
|
||||
) : (
|
||||
<XCircle className="h-3.5 w-3.5 text-muted-foreground" aria-hidden />
|
||||
)}
|
||||
{form.status ? "Active customer" : "Inactive customer"}
|
||||
</Label>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">
|
||||
Turn off when this customer no longer works for
|
||||
the mine. Historical quotes and orders are kept;
|
||||
the customer stops appearing on active pickers.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="customer-status"
|
||||
checked={!!form.status}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm({ ...form, status: checked === true })
|
||||
}
|
||||
aria-label="Active customer"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldLabel icon={FileText} label="Description" />
|
||||
<Textarea
|
||||
|
|
|
|||
Loading…
Reference in a new issue