Updating foreign keys

This commit is contained in:
2025-04-22 01:13:09 +02:00
parent 272a1f61af
commit f0bf294d3d

View File

@@ -1,15 +1,23 @@
import {FormContextType, RJSFSchema, WidgetProps} from '@rjsf/utils'; import { FormContextType, RJSFSchema, UiSchema, WidgetProps } from '@rjsf/utils';
import { Autocomplete, CircularProgress, TextField } from "@mui/material"; import { Autocomplete, Button, CircularProgress, Container, Grid2, InputAdornment, Modal, TextField, Box } from "@mui/material";
import ClearIcon from '@mui/icons-material/Clear';
import EditIcon from '@mui/icons-material/Edit';
import NoteAddIcon from '@mui/icons-material/NoteAdd';
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
import { useList, useOne } from "@refinedev/core"; import { useList, useOne } from "@refinedev/core";
import { ResourceContext } from "../../contexts/ResourceContext"; import { ResourceContext } from "../../contexts/ResourceContext";
import { CrudForm } from "../crud-form";
type ForeignKeyReference = {
resource: string,
label: string,
displayedFields: [string],
schema: string
}
type ForeignKeySchema = RJSFSchema & { type ForeignKeySchema = RJSFSchema & {
foreignKey?: { foreignKey?: {
reference: { reference: ForeignKeyReference
resource: string,
label: string
}
} }
} }
@@ -19,26 +27,105 @@ export default function ForeignKeyWidget<T = any, S extends ForeignKeySchema = F
if (props.schema.foreignKey === undefined) { if (props.schema.foreignKey === undefined) {
return; return;
} }
const { onChange, value: originalValue } = props;
const [initialState, setInitialState] = useState(true);
if (originalValue != null && initialState) {
return <InitialValue {...props} onClear={() => {setInitialState(false); onChange(null)}}/>
}
return ( const { value: originalValue, onChange } = props;
<RealAutocomplete {...props}/> const [currentValue, setCurrentValue] = useState(originalValue)
)
if (currentValue) {
return <ChosenValue {...props} onClear={() => {setCurrentValue(null); onChange(null)}}/>
}
return <RealAutocomplete {...props} onChange={(value) => {setCurrentValue(value); onChange(value)}}/>
}; };
const InitialValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>( const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
props: WidgetProps<T, S, F>
) => {
if (props.schema.foreignKey === undefined) {
return;
}
const { onChange, label } = props
const [openFormModal, setOpenFormModal] = useState(false);
const [searchString, setSearchString] = useState<string>("");
const [debouncedInputValue, setDebouncedInputValue] = useState<string>();
useEffect(() => {
const handler = setTimeout(() => setDebouncedInputValue(searchString), 300); // Adjust debounce delay as needed
return () => clearTimeout(handler);
}, [searchString]);
const { resource, schema, label: labelField = "label" } = props.schema.foreignKey.reference
const { basePath } = useContext(ResourceContext)
const { data, isLoading } = useList({
resource: `${basePath}/${resource}`,
pagination: { current: 1, pageSize: 10, mode: "server" },
filters: [{ field: "label", operator: "contains", value: debouncedInputValue }],
sorters: [{ field: "label", order: "asc" }],
});
return (
<>
<Autocomplete
onChange={(event, value) => {
onChange(value ? value.id : null);
return true;
}}
onInputChange={(event, value) => {
setSearchString(value)
}}
options={data ? data.data : []}
getOptionLabel={(option) => option ? option[labelField] : ""}
loading={isLoading}
forcePopupIcon={false}
renderInput={(params) => (
<TextField
{...params}
label={ label }
variant="outlined"
slotProps={{
input: {
...params.InputProps,
endAdornment: (
<Button variant="outlined" onClick={() => setOpenFormModal(true)} color="success" >
<NoteAddIcon />
</Button>
),
},
}}
/>
)}
/>
<Modal
open={openFormModal}
onClose={() => setOpenFormModal(false)}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<FormContainer
schemaName={schema}
resourceBasePath={basePath}
resource={resource}
uiSchema={{}}
onSuccess={(data: any) => {
setOpenFormModal(false)
onChange(data.data.id);
}}
/>
</Modal>
</>
);
}
const ChosenValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
props: WidgetProps<T, S, F> & { onClear: () => void } props: WidgetProps<T, S, F> & { onClear: () => void }
) => { ) => {
const { onClear, value } = props; const { onClear, value } = props;
const [openFormModal, setOpenFormModal] = React.useState(false);
if (props.schema.foreignKey === undefined) { if (props.schema.foreignKey === undefined) {
return; return;
} }
const { resource, label: labelField = "label" } = props.schema.foreignKey.reference const { resource, schema, label: labelField = "label", displayedFields } = props.schema.foreignKey.reference
const { basePath } = useContext(ResourceContext) const { basePath } = useContext(ResourceContext)
const { data, isLoading } = useOne({ const { data, isLoading } = useOne({
@@ -51,60 +138,97 @@ const InitialValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F
} }
return ( return (
<Autocomplete <>
value={data.data} <TextField label={ props.label } variant="outlined" disabled={true} value={data.data[labelField]}
onChange={() => onClear()} slotProps={{
onInputChange={() => onClear()} input: {
options={[data.data]} endAdornment: (
getOptionLabel={(option) => option ? option[labelField] : ""} <InputAdornment position="end">
loading={isLoading} <Button variant="outlined" onClick={() => setOpenFormModal(true)} color="primary" >
renderInput={(params) => ( <EditIcon />
<TextField {...params} label={ props.label } variant="outlined" /> </Button>
)} <Button variant="outlined" onClick={onClear} color="error" >
/> <ClearIcon />
</Button>
</InputAdornment>),
},
}}
/>
{ displayedFields && <Preview id={value} basePath={basePath} resource={resource} displayedFields={displayedFields}/>}
<Modal
open={openFormModal}
onClose={() => setOpenFormModal(false)}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<FormContainer
schemaName={schema}
resourceBasePath={basePath}
resource={resource}
uiSchema={{}}
id={value}
onSuccess={() => setOpenFormModal(false)}
/>
</Modal>
</>
) )
} }
const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>( const modalStyle = {
props: WidgetProps<T, S, F> position: 'absolute',
) => { top: '50%',
if (props.schema.foreignKey === undefined) { left: '50%',
return; transform: 'translate(-50%, -50%)',
} width: 400,
const { onChange } = props bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
pt: 2,
px: 4,
pb: 3,
};
const [searchString, setSearchString] = useState<string>(""); type FormContainerProps = {
const [debouncedInputValue, setDebouncedInputValue] = useState<string>(); schemaName: string,
useEffect(() => { resourceBasePath: string,
const handler = setTimeout(() => setDebouncedInputValue(searchString), 300); // Adjust debounce delay as needed resource: string,
return () => clearTimeout(handler); uiSchema?: UiSchema,
}, [searchString]); id?: string,
onSuccess: (data: any) => void
}
const { resource, label: labelField = "label" } = props.schema.foreignKey.reference const FormContainer = (props: FormContainerProps) => {
const { basePath } = useContext(ResourceContext) const { schemaName, resourceBasePath, resource, uiSchema = {}, id = undefined, onSuccess } = props;
const { data, isLoading } = useList({
resource: `${basePath}/${resource}`,
pagination: { current: 1, pageSize: 10, mode: "server" },
filters: [{ field: "label", operator: "contains", value: debouncedInputValue }],
sorters: [{ field: "label", order: "asc" }],
});
return ( return (
<Autocomplete <Box sx={{ ...modalStyle, width: 800 }}>
onChange={(event, value) => { <CrudForm schemaName={schemaName} resourceBasePath={resourceBasePath} resource={resource} uiSchema={uiSchema} id={id} onSuccess={onSuccess} />
onChange(value ? value.id : null); </Box>
return true; )
}} }
onInputChange={(event, newInputValue) => {
setSearchString(newInputValue) const Preview = (props: {id: string, resource: string, basePath: string, displayedFields: [string]}) => {
console.log(newInputValue) const { basePath, resource, id, displayedFields } = props
}}
options={data ? data.data : []} const { data, isLoading } = useOne({
getOptionLabel={(option) => option ? option[labelField] : ""} resource: `${basePath}/${resource}`,
loading={isLoading} id
renderInput={(params) => ( });
<TextField {...params} label={ props.label } variant="outlined" />
)} if (isLoading || data === undefined) {
/> return <CircularProgress />
}
return (
<Grid2 container spacing={2}>
{displayedFields.map((field: string) => {
return (
<>
<Grid2 size={2}><Container>{field}</Container></Grid2>
<Grid2 size={9}><Container dangerouslySetInnerHTML={{ __html: data.data[field] }} ></Container></Grid2>
</>
)
})}
</Grid2>
); );
} }