100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import React, { useState, useMemo } from "react";
|
|
import { TextField, Autocomplete, Chip, Box } from "@mui/material";
|
|
import DoneIcon from "@mui/icons-material/Done";
|
|
import type { FieldConfig } from "../../../types";
|
|
|
|
interface Props {
|
|
field: FieldConfig;
|
|
value: any;
|
|
onChange: (value: any) => void;
|
|
fkOptions?: { value: any; label: string }[];
|
|
fkLoading?: boolean;
|
|
onOpen?: () => void;
|
|
}
|
|
|
|
export function FkMultiSelectField({ field, value, onChange, fkOptions, fkLoading, onOpen }: Props) {
|
|
const [open, setOpen] = useState(false);
|
|
const [frozenValue, setFrozenValue] = useState<any[]>([]);
|
|
|
|
const handleOpen = () => {
|
|
onOpen?.();
|
|
setFrozenValue(value ?? []);
|
|
setOpen(true);
|
|
};
|
|
|
|
const handleClose = () => {
|
|
setOpen(false);
|
|
};
|
|
|
|
const sortedOptions = useMemo(() => {
|
|
const sel = new Set(frozenValue);
|
|
const picked: { value: any; label: string }[] = [];
|
|
const rest: { value: any; label: string }[] = [];
|
|
for (const opt of fkOptions ?? []) {
|
|
(sel.has(opt.value) ? picked : rest).push(opt);
|
|
}
|
|
return [...picked, ...rest];
|
|
}, [fkOptions, frozenValue]);
|
|
|
|
return (
|
|
<Autocomplete
|
|
multiple
|
|
disableCloseOnSelect
|
|
open={open}
|
|
onOpen={handleOpen}
|
|
onClose={handleClose}
|
|
options={sortedOptions}
|
|
getOptionLabel={(o) => o.label}
|
|
value={fkOptions?.filter((o) => (value ?? []).includes(o.value)) ?? []}
|
|
onChange={(_, newVal) => onChange(newVal.map((v) => v.value))}
|
|
loading={fkLoading}
|
|
renderOption={(props, option, { selected }) => (
|
|
<li {...props}>
|
|
{selected ? (
|
|
<DoneIcon sx={{ fontSize: 14, mr: 1, color: "primary.main" }} />
|
|
) : (
|
|
<Box sx={{ width: 22, mr: 1 }} />
|
|
)}
|
|
{option.label}
|
|
</li>
|
|
)}
|
|
renderTags={(tagValue, getTagProps) => {
|
|
const maxChips = 1;
|
|
return (
|
|
<>
|
|
{tagValue.slice(0, maxChips).map((tag, index) => {
|
|
const { key, ...tagProps } = getTagProps({ index });
|
|
return (
|
|
<Chip
|
|
key={key}
|
|
{...tagProps}
|
|
label={tag.label.length > 10 ? `${tag.label.slice(0, 8)}..` : tag.label}
|
|
size="small"
|
|
onClick={open ? handleClose : handleOpen}
|
|
sx={{ cursor: "pointer" }}
|
|
/>
|
|
);
|
|
})}
|
|
{tagValue.length > maxChips && (
|
|
<Chip
|
|
label={`+${tagValue.length - maxChips}`}
|
|
size="small"
|
|
onClick={open ? handleClose : handleOpen}
|
|
sx={{ cursor: "pointer" }}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}}
|
|
renderInput={(params) => (
|
|
<TextField {...params} label={field.label} helperText={field.description || undefined} size="small" />
|
|
)}
|
|
size="small"
|
|
sx={{
|
|
"& .MuiAutocomplete-popupIndicator, & .MuiAutocomplete-clearIndicator": { width: 20, height: 20, fontSize: 16 },
|
|
}}
|
|
disabled={field.readOnly}
|
|
/>
|
|
);
|
|
}
|