Files
app/src/admin/components/warehouse/ItemPicker.tsx
2026-06-07 00:21:17 +02:00

104 lines
3.2 KiB
TypeScript

import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import Autocomplete from "@mui/material/Autocomplete";
import TextField from "@mui/material/TextField";
import CircularProgress from "@mui/material/CircularProgress";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import {
warehouseItemListOptions,
type WarehouseItem,
} from "../../lib/queries/warehouse";
interface ItemPickerProps {
value: number | null;
onChange: (itemId: number) => void;
itemName?: string;
}
/** Minimal option shape used to display a selected value that isn't in the
* current search results (edit/prefill via `itemName`). */
type ItemOption = Pick<WarehouseItem, "id" | "name"> & Partial<WarehouseItem>;
export default function ItemPicker({
value,
onChange,
itemName,
}: ItemPickerProps) {
const [inputValue, setInputValue] = useState(itemName ?? "");
const { data, isFetching } = useQuery(
warehouseItemListOptions({ search: inputValue, perPage: 20 }),
);
const options: ItemOption[] = data?.data ?? [];
// Show the selected value even when it isn't in the current search results
// (e.g. prefilled via `itemName`) by synthesizing a fallback option.
const selectedOption: ItemOption | null =
value == null
? null
: (options.find((o) => o.id === value) ?? {
id: value,
name: itemName ?? "",
});
return (
<Autocomplete<ItemOption>
value={selectedOption}
inputValue={inputValue}
onInputChange={(_, newInput) => setInputValue(newInput)}
onChange={(_, opt) => {
if (opt) onChange(opt.id);
}}
options={options}
loading={isFetching}
filterOptions={(x) => x}
getOptionLabel={(o) => o.name}
isOptionEqualToValue={(o, v) => o.id === v.id}
noOptionsText="Žádné položky"
renderOption={(props, option) => {
const { key, ...rest } = props;
return (
<Box component="li" key={key} {...rest}>
<Box>
<Typography variant="body2">{option.name}</Typography>
{(option.item_number ||
option.available_quantity !== undefined) && (
<Typography variant="caption" color="text.secondary">
{option.item_number}
{option.item_number && option.available_quantity !== undefined
? " · "
: ""}
{option.available_quantity !== undefined
? `${option.available_quantity} ${option.unit ?? ""}`
: ""}
</Typography>
)}
</Box>
</Box>
);
}}
renderInput={(params) => (
<TextField
{...params}
size="small"
placeholder="Hledat položku..."
slotProps={{
input: {
...params.InputProps,
endAdornment: (
<>
{isFetching ? (
<CircularProgress color="inherit" size={18} />
) : null}
{params.InputProps.endAdornment}
</>
),
},
}}
/>
)}
/>
);
}