new react-openapi

This commit is contained in:
2026-07-13 04:35:58 +05:30
parent 617f6bea6c
commit 72e7e843a4
24 changed files with 1081 additions and 297 deletions

View File

@@ -1,5 +1,6 @@
import React from "react";
import { TextField, Autocomplete } from "@mui/material";
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 {
@@ -12,20 +13,86 @@ interface Props {
}
export function FkMultiSelectField({ field, value, onChange, fkOptions, fkLoading, onOpen }: Props) {
console.log(`[FkMultiSelectField] render field="${field.name}" fkOptions=${fkOptions ? `${fkOptions.length} items` : "undefined"} fkLoading=${fkLoading} value=${JSON.stringify(value)}`);
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
options={fkOptions ?? []}
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))}
onOpen={() => onOpen?.()}
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} size="small" />
<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}
/>
);