- 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>
90 lines
2.8 KiB
TypeScript
90 lines
2.8 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 { Supplier } from "../lib/queries/issued-orders";
|
|
|
|
interface SupplierPickerProps {
|
|
suppliers: Supplier[];
|
|
/** Selected supplier 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 supplier name AND the IČO, so typing either finds the
|
|
// record. Match on a stringified "name + ico" so MUI's default
|
|
// case-insensitive substring filtering covers both.
|
|
const filterSuppliers = createFilterOptions<Supplier>({
|
|
stringify: (s) => `${s.name} ${s.ico ?? ""}`,
|
|
});
|
|
|
|
/**
|
|
* Searchable supplier picker for issued orders (purchase orders) — a
|
|
* controlled MUI Autocomplete over the sklad_suppliers lookup list, bound to
|
|
* the supplier id. Modeled on CustomerPicker (offers/invoices keep that one);
|
|
* renders bare (no label/error text); wrap it in the page's
|
|
* <Field label error> for the label + error line.
|
|
*/
|
|
export default function SupplierPicker({
|
|
suppliers,
|
|
value,
|
|
onChange,
|
|
disabled,
|
|
error,
|
|
placeholder,
|
|
id,
|
|
}: SupplierPickerProps) {
|
|
const selected = suppliers.find((s) => s.id === value) ?? null;
|
|
return (
|
|
<Autocomplete<Supplier>
|
|
options={suppliers}
|
|
value={selected}
|
|
onChange={(_, opt) => onChange(opt ? opt.id : null)}
|
|
getOptionLabel={(s) => s?.name ?? ""}
|
|
isOptionEqualToValue={(o, v) => o.id === v.id}
|
|
filterOptions={filterSuppliers}
|
|
disabled={disabled}
|
|
size="small"
|
|
fullWidth
|
|
autoHighlight
|
|
noOptionsText="Žádní dodavatelé"
|
|
renderOption={(props, s) => {
|
|
const { key, ...rest } = props as typeof props & { key: string };
|
|
return (
|
|
<Box component="li" key={key} {...rest}>
|
|
<Box>
|
|
{s.name}
|
|
{s.ico && (
|
|
<Typography
|
|
variant="caption"
|
|
sx={{ display: "block", color: "text.secondary" }}
|
|
>
|
|
IČ: {s.ico}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}}
|
|
renderInput={(params) => (
|
|
<MuiTextField
|
|
{...params}
|
|
id={id}
|
|
placeholder={placeholder ?? "Vyberte dodavatele…"}
|
|
error={!!error}
|
|
/>
|
|
)}
|
|
/>
|
|
);
|
|
}
|