Files
app/src/admin/ui/CustomerPicker.tsx
BOHA 4d0ec53514 feat(shell): scrollable status tabs, per-page tab titles, skip-to-content, Czech MUI locale
- Shared Tabs: variant=scrollable + auto scroll buttons — the 5 status-filter
  tabs no longer clip unreachable at ~360px (desktop rendering unchanged).
- TitleSync: browser tab shows the active page ('Faktury · BOHA'; ambiguous
  labels qualified with their section, e.g. 'Záznam – Docházka · BOHA');
  index.html fallback now 'BOHA Admin'.
- Skip-to-content link (visually hidden, visible on focus) + id/tabIndex on
  <main> — keyboard users skip the 248px sidebar.
- MUI csCZ locale merged into the theme (kills built-in English strings like
  'No options'); Czech noOptionsText on Customer/Supplier pickers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 03:22:06 +02:00

92 lines
2.9 KiB
TypeScript

import Autocomplete, { createFilterOptions } from "@mui/material/Autocomplete";
import MuiTextField from "@mui/material/TextField";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import type { Customer } from "../lib/queries/offers";
interface CustomerPickerProps {
customers: Customer[];
/** Selected customer id (FK), or null when none chosen. */
value: number | null;
onChange: (id: number | null) => void;
disabled?: boolean;
/**
* When set, draws the input in its error state (red border). The error TEXT
* is owned by the surrounding <Field> wrapper, so we deliberately do NOT
* render helperText here to avoid duplicating the message.
*/
error?: string;
placeholder?: string;
/** Optional id forwarded by <Field> for label association. */
id?: string;
}
// Search matches the customer name AND the IČO (company_id), so typing either
// finds the record. Match on a stringified "name + company_id" so MUI's default
// case-insensitive substring filtering covers both.
const filterCustomers = createFilterOptions<Customer>({
stringify: (c) => `${c.name} ${c.company_id ?? ""}`,
});
/**
* Shared searchable customer picker — a controlled MUI Autocomplete over the
* customers list, bound to the customer id. Used by Offers, Invoices and Issued
* Orders so all three FK customer selectors behave identically. Renders bare
* (no label/error text); wrap it in the page's <Field label error> for the
* label + error line.
*/
export default function CustomerPicker({
customers,
value,
onChange,
disabled,
error,
placeholder,
id,
}: CustomerPickerProps) {
const selected = customers.find((c) => c.id === value) ?? null;
return (
<Autocomplete<Customer>
options={customers}
value={selected}
onChange={(_, opt) => onChange(opt ? opt.id : null)}
getOptionLabel={(c) => c?.name ?? ""}
isOptionEqualToValue={(o, v) => o.id === v.id}
filterOptions={filterCustomers}
disabled={disabled}
size="small"
fullWidth
autoHighlight
noOptionsText="Žádní zákazníci"
renderOption={(props, c) => {
const { key, ...rest } = props as typeof props & { key: string };
return (
<Box component="li" key={key} {...rest}>
<Box>
{c.name}
{(c.company_id || c.city) && (
<Typography
variant="caption"
sx={{ display: "block", color: "text.secondary" }}
>
{c.company_id && `IČ: ${c.company_id}`}
{c.company_id && c.city && " · "}
{c.city}
</Typography>
)}
</Box>
</Box>
);
}}
renderInput={(params) => (
<MuiTextField
{...params}
id={id}
placeholder={placeholder ?? "Vyberte zákazníka…"}
error={!!error}
/>
)}
/>
);
}