Compare commits
2 Commits
7b6ca62d9a
...
f0bf294d3d
| Author | SHA1 | Date | |
|---|---|---|---|
| f0bf294d3d | |||
| 272a1f61af |
@@ -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) {
|
||||
|
||||
@@ -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 { onChange } = props
|
||||
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,
|
||||
};
|
||||
|
||||
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]);
|
||||
type FormContainerProps = {
|
||||
schemaName: string,
|
||||
resourceBasePath: string,
|
||||
resource: string,
|
||||
uiSchema?: UiSchema,
|
||||
id?: string,
|
||||
onSuccess: (data: any) => void
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"} />
|
||||
}
|
||||
|
||||
@@ -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"} />
|
||||
}
|
||||
|
||||
@@ -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"} />
|
||||
}
|
||||
|
||||
@@ -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"} />
|
||||
}
|
||||
|
||||
@@ -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"} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user