Compare commits

..

2 Commits

Author SHA1 Message Date
f0bf294d3d Updating foreign keys 2025-04-22 01:13:09 +02:00
272a1f61af Dynamic Schema names for crud 2025-04-22 00:31:39 +02:00
7 changed files with 200 additions and 75 deletions

View File

@@ -37,6 +37,7 @@ export const CrudForm: React.FC<CrudFormProps> = (props) => {
onMutationSuccess: (data: any) => { if (onSuccess) { onSuccess(data) } },
});
const schemaValue = id === undefined ? `${schemaName}Create` : `${schemaName}Update`;
const record = query?.data?.data;
const [formData, setFormData] = useState(record);
@@ -46,7 +47,7 @@ export const CrudForm: React.FC<CrudFormProps> = (props) => {
useEffect(() => {
const fetchSchema = async () => {
try {
const resourceSchema = await jsonschemaProvider.getResourceSchema(schemaName);
const resourceSchema = await jsonschemaProvider.getResourceSchema(schemaValue);
setSchema(resourceSchema);
setLoading(false);
} catch (error) {

View File

@@ -1,15 +1,23 @@
import {FormContextType, RJSFSchema, WidgetProps} from '@rjsf/utils';
import { Autocomplete, CircularProgress, TextField } from "@mui/material";
import { FormContextType, RJSFSchema, UiSchema, WidgetProps } from '@rjsf/utils';
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 { useList, useOne } from "@refinedev/core";
import { ResourceContext } from "../../contexts/ResourceContext";
import { CrudForm } from "../crud-form";
type ForeignKeyReference = {
resource: string,
label: string,
displayedFields: [string],
schema: string
}
type ForeignKeySchema = RJSFSchema & {
foreignKey?: {
reference: {
resource: string,
label: string
}
reference: ForeignKeyReference
}
}
@@ -19,26 +27,105 @@ export default function ForeignKeyWidget<T = any, S extends ForeignKeySchema = F
if (props.schema.foreignKey === undefined) {
return;
}
const { onChange, value: originalValue } = props;
const [initialState, setInitialState] = useState(true);
if (originalValue != null && initialState) {
return <InitialValue {...props} onClear={() => {setInitialState(false); onChange(null)}}/>
}
return (
<RealAutocomplete {...props}/>
)
const { value: originalValue, onChange } = 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 }
) => {
const { onClear, value } = props;
const [openFormModal, setOpenFormModal] = React.useState(false);
if (props.schema.foreignKey === undefined) {
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 { data, isLoading } = useOne({
@@ -51,60 +138,97 @@ const InitialValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F
}
return (
<Autocomplete
value={data.data}
onChange={() => onClear()}
onInputChange={() => onClear()}
options={[data.data]}
getOptionLabel={(option) => option ? option[labelField] : ""}
loading={isLoading}
renderInput={(params) => (
<TextField {...params} label={ props.label } variant="outlined" />
)}
<>
<TextField label={ props.label } variant="outlined" disabled={true} value={data.data[labelField]}
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">
<Button variant="outlined" onClick={() => setOpenFormModal(true)} color="primary" >
<EditIcon />
</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>(
props: WidgetProps<T, S, F>
) => {
if (props.schema.foreignKey === undefined) {
return;
const modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
pt: 2,
px: 4,
pb: 3,
};
type FormContainerProps = {
schemaName: string,
resourceBasePath: string,
resource: string,
uiSchema?: UiSchema,
id?: string,
onSuccess: (data: any) => void
}
const { onChange } = props
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, 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" }],
});
const FormContainer = (props: FormContainerProps) => {
const { schemaName, resourceBasePath, resource, uiSchema = {}, id = undefined, onSuccess } = props;
return (
<Autocomplete
onChange={(event, value) => {
onChange(value ? value.id : null);
return true;
}}
onInputChange={(event, newInputValue) => {
setSearchString(newInputValue)
console.log(newInputValue)
}}
options={data ? data.data : []}
getOptionLabel={(option) => option ? option[labelField] : ""}
loading={isLoading}
renderInput={(params) => (
<TextField {...params} label={ props.label } variant="outlined" />
)}
/>
<Box sx={{ ...modalStyle, width: 800 }}>
<CrudForm schemaName={schemaName} resourceBasePath={resourceBasePath} resource={resource} uiSchema={uiSchema} id={id} onSuccess={onSuccess} />
</Box>
)
}
const Preview = (props: {id: string, resource: string, basePath: string, displayedFields: [string]}) => {
const { basePath, resource, id, displayedFields } = props
const { data, isLoading } = useOne({
resource: `${basePath}/${resource}`,
id
});
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>
);
}

View File

@@ -26,9 +26,9 @@ const ListContract = () => {
}
const EditContract = () => {
return <Edit<Contract> resource={`contracts`} schemaName={"ContractUpdate"} />
return <Edit<Contract> resource={`contracts`} schemaName={"Contract"} />
}
const CreateContract = () => {
return <New<Contract> resource={`contracts`} schemaName={"ContractCreate"} />
return <New<Contract> resource={`contracts`} schemaName={"Contract"} />
}

View File

@@ -26,9 +26,9 @@ const ListDraft = () => {
}
const EditDraft = () => {
return <Edit<Draft> resource={`contracts/drafts`} schemaName={"ContractDraftUpdate"} />
return <Edit<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} />
}
const CreateDraft = () => {
return <New<Draft> resource={`contracts/drafts`} schemaName={"ContractDraftCreate"} />
return <New<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} />
}

View File

@@ -29,9 +29,9 @@ const ListEntity = () => {
}
const EditEntity = () => {
return <Edit<Entity> resource={`entities`} schemaName={"EntityUpdate"} />
return <Edit<Entity> resource={`entities`} schemaName={"Entity"} />
}
const CreateEntity = () => {
return <New<Entity> resource={`entities`} schemaName={"EntityCreate"} />
return <New<Entity> resource={`entities`} schemaName={"Entity"} />
}

View File

@@ -27,9 +27,9 @@ const ListProvision = () => {
}
const EditProvision = () => {
return <Edit<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplateUpdate"} />
return <Edit<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplate"} />
}
const CreateProvision = () => {
return <New<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplateCreate"} />
return <New<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplate"} />
}

View File

@@ -26,9 +26,9 @@ const ListTemplate = () => {
}
const EditTemplate = () => {
return <Edit<Template> resource={`templates/contracts`} schemaName={"ContractTemplateUpdate"} />
return <Edit<Template> resource={`templates/contracts`} schemaName={"ContractTemplate"} />
}
const CreateTemplate = () => {
return <New<Template> resource={`templates/contracts`} schemaName={"ContractTemplateCreate"} />
return <New<Template> resource={`templates/contracts`} schemaName={"ContractTemplate"} />
}