104 lines
3.2 KiB
TypeScript
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}
|
|
</>
|
|
),
|
|
},
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
);
|
|
}
|