Compare commits

...

9 Commits

23 changed files with 418 additions and 162 deletions

View File

@@ -210,14 +210,14 @@ def replace_variables_in_value(variables, value: str):
class ContractDraftFilters(FilterSchema):
status: Optional[str] = None
status__in: Optional[list[str]] = None
class Constants(Filter.Constants):
model = ContractDraft
search_model_fields = ["label"]
class ContractFilters(FilterSchema):
status: Optional[str] = None
status__in: Optional[list[str]] = None
class Constants(Filter.Constants):
model = Contract

View File

@@ -159,7 +159,7 @@ class FilterSchema(Filter):
order_by: Optional[list[str]] = None
created_at__lte: Optional[str] = None
created_at__gte: Optional[str] = None
created_by__in: Optional[str] = None
created_by__in: Optional[list[str]] = None
updated_at__lte: Optional[str] = None
updated_at__gte: Optional[str] = None
updated_by__in: Optional[str] = None
updated_by__in: Optional[list[str]] = None

View File

@@ -5,7 +5,9 @@ from pydantic import BaseModel, Field
class Reader(BaseModel):
id: Optional[PydanticObjectId] = Field(default=None, validation_alias="_id")
id: Optional[PydanticObjectId] = Field(validation_alias="_id")
created_by: PydanticObjectId = Field(title="Créé par")
updated_by: PydanticObjectId = Field(title="Modifié par")
@classmethod
def from_model(cls, model):

View File

@@ -1,7 +1,7 @@
from typing import Any
from typing import Any, Optional
from beanie import PydanticObjectId
from pydantic import Field
from pydantic import Field, BaseModel
from firm.core.models import CrudDocument, CrudDocumentConfig
from firm.core.schemas import Writer, Reader
@@ -32,7 +32,8 @@ class CurrentFirm(CrudDocument):
return cls.model_validate(document)
class CurrentFirmSchemaRead(Reader):
class CurrentFirmSchemaRead(BaseModel):
id: Optional[PydanticObjectId]
entity: EntityRead
partner: EntityRead
partner_list: list[EntityRead]

View File

@@ -83,7 +83,7 @@ class Entity(CrudDocument):
class EntityDataFilter(Filter):
type__in: Optional[list[EntityTypeEnum]] = None
type__in: Optional[list[str]] = None
class Constants(Filter.Constants):
model = EntityType

View File

@@ -42,7 +42,7 @@ class ProvisionTemplateReference(BaseModel):
provision_template_id: PydanticObjectId = ForeignKey(
"templates/provisions",
"TemplateProvision",
"ProvisionTemplate",
['title', 'body'],
props={"parametrized": True},
title="Template de clause"

View File

@@ -7,6 +7,8 @@ import ArrayFieldTemplate from "./templates/ArrayFieldTemplate"
import ArrayFieldItemTemplate from "./templates/ArrayFieldItemTemplate";
import { ResourceContext } from "../contexts/ResourceContext";
import { ReactNode } from "react";
import { ParametersContextProvider } from "../contexts/parameters-context";
import CrudArrayField from "./fields/crud-array-field";
type BaseFormProps = {
schema: RJSFSchema,
@@ -19,11 +21,12 @@ type BaseFormProps = {
}
export const customWidgets: RegistryWidgetsType = {
TextWidget: CrudTextWidget
TextWidget: CrudTextWidget,
};
export const customFields: RegistryFieldsType = {
AnyOfField: UnionEnumField
AnyOfField: UnionEnumField,
ArrayField: CrudArrayField
}
const customTemplates = {
@@ -36,19 +39,21 @@ export const BaseForm: React.FC<BaseFormProps> = (props) => {
return (
<ResourceContext.Provider value={{basePath: resourceBasePath}} >
<Form
schema={schema}
uiSchema={uiSchema === undefined ? {} : uiSchema}
formData={formData}
onSubmit={(e, id) => onSubmit != undefined && onSubmit(e.formData)}
validator={validator}
omitExtraData={true}
widgets={customWidgets}
fields={customFields}
templates={customTemplates}
onChange={(e, id) => onChange != undefined && onChange(e.formData)}
children={children}
/>
<ParametersContextProvider>
<Form
schema={schema}
uiSchema={uiSchema === undefined ? {} : uiSchema}
formData={formData}
onSubmit={(e, id) => onSubmit != undefined && onSubmit(e.formData)}
validator={validator}
omitExtraData={true}
widgets={customWidgets}
fields={customFields}
templates={customTemplates}
onChange={(e, id) => onChange != undefined && onChange(e.formData)}
children={children}
/>
</ParametersContextProvider>
</ResourceContext.Provider>
)
}

View File

@@ -1,10 +1,8 @@
import { useEffect, useState } from "react";
import { jsonschemaProvider } from "../providers/jsonschema-provider";
import { Accordion, AccordionDetails, AccordionSummary, CircularProgress } from "@mui/material";
import FilterForm from "../../filter-form/components/filter-form";
import TextField from "@mui/material/TextField";
import { useSearchParams } from "react-router";
import { GridExpandMoreIcon } from "@mui/x-data-grid";
import { useResourceFilter } from "../hook";
export type OnChangeValue = {
search: string | null
@@ -19,25 +17,7 @@ type CrudFiltersProps = {
const CrudFilters = (props: CrudFiltersProps) => {
const { resourceName, resourcePath, onChange } = props
const [hasSearch, setHasSearch] = useState(false)
const [filtersSchema, setFiltersSchema] = useState<any[]>([])
const [filtersLoading, setFiltersLoading] = useState(true);
useEffect(() => {
const fetchSchema = async () => {
try {
setHasSearch(await jsonschemaProvider.hasSearch(resourcePath))
const resourceFilters = await jsonschemaProvider.getListFilters(resourceName, resourcePath)
setFiltersSchema(resourceFilters);
console.log(resourceFilters);
setFiltersLoading(false);
} catch (error) {
console.error('Error fetching data:', error);
setFiltersLoading(false);
}
};
fetchSchema();
}, []);
const { hasSearch, filtersSchema, filtersLoading } = useResourceFilter(resourceName, resourcePath)
if (filtersLoading) {
return <CircularProgress />
@@ -82,8 +62,6 @@ type SearchFilter = {
const SearchFilter = (props: SearchFilter) => {
const {value, onChange} = props;
const [searchParams, setSearchParams] = useSearchParams();
return (
<TextField
label="schemas.search"

View File

@@ -1,9 +1,8 @@
import { ReactNode, useEffect, useState } from "react";
import { ReactNode } from "react";
import { CircularProgress } from "@mui/material";
import { useForm } from "@refinedev/core";
import { UiSchema } from "@rjsf/utils";
import { jsonschemaProvider } from "../providers/jsonschema-provider";
import { BaseForm } from "./base-form";
import { useResourceSchema } from "../hook";
type CrudFormProps = {
schemaName: string,
@@ -18,31 +17,8 @@ type CrudFormProps = {
export const CrudForm: React.FC<CrudFormProps> = (props) => {
const { schemaName, uiSchema, record, resourceBasePath, defaultValue, children, onSubmit=(data: any) => {}, card=false } = props;
const [schema, setSchema] = useState({});
const [schemaLoading, setSchemaLoading] = useState(true);
useEffect(() => {
const fetchSchema = async () => {
try {
let resourceSchema
if (record === undefined) {
resourceSchema = await jsonschemaProvider.getCreateResourceSchema(schemaName);
} else {
if (card) {
resourceSchema = await jsonschemaProvider.getCardResourceSchema(schemaName);
} else {
resourceSchema = await jsonschemaProvider.getUpdateResourceSchema(schemaName);
}
}
setSchema(resourceSchema);
setSchemaLoading(false);
} catch (error) {
console.error('Error fetching data:', error);
setSchemaLoading(false);
}
};
fetchSchema();
}, []);
const type = record === undefined ? "create" : card ? "card" : "update"
const { schema, schemaLoading } = useResourceSchema(schemaName, type);
if(schemaLoading) {
return <CircularProgress />

View File

@@ -1,8 +1,7 @@
import { CircularProgress } from "@mui/material";
import { DataGrid, GridColDef, GridColumnVisibilityModel, GridValidRowModel } from "@mui/x-data-grid";
import { UiSchema } from "@rjsf/utils";
import React, { useEffect, useState } from "react";
import { jsonschemaProvider } from "../providers/jsonschema-provider";
import { CircularProgress } from "@mui/material";
import { useResourceColumns } from "../hook";
type CrudListProps = {
schemaName: string,
@@ -32,24 +31,9 @@ const CrudList = <T extends GridValidRowModel>(props: CrudListProps) => {
columnDefinitions
} = props;
const [columnSchema, setColumnSchema] = useState<ColumnSchema<T>>()
const [schemaLoading, setSchemaLoading] = useState(true);
useEffect(() => {
const fetchSchema = async () => {
try {
const resourceColumns = await jsonschemaProvider.getReadResourceColumns(schemaName)
const definedColumns = computeColumnSchema<T>(columnDefinitions, resourceColumns)
setColumnSchema(definedColumns);
setSchemaLoading(false);
} catch (error) {
console.error('Error fetching data:', error);
setSchemaLoading(false);
}
};
fetchSchema();
}, []);
const { columnSchema, columnLoading } = useResourceColumns<T>(schemaName, columnDefinitions);
if (schemaLoading || columnSchema === undefined) {
if (columnLoading || columnSchema === undefined) {
return <CircularProgress />
}
@@ -69,26 +53,4 @@ const CrudList = <T extends GridValidRowModel>(props: CrudListProps) => {
)
}
function computeColumnSchema<T extends GridValidRowModel>(definitionColumns: ColumnDefinition[], resourceColumns: GridColDef[]): ColumnSchema<T> {
//reorder resourceColumns as in definition
definitionColumns.slice().reverse().forEach(first => {
resourceColumns.sort(function(x,y){ return x.field == first.field ? -1 : y.field == first.field ? 1 : 0; });
})
let visibilityModel: GridColumnVisibilityModel = {}
resourceColumns.forEach((resource, index) =>{
visibilityModel[resource.field] = definitionColumns.some(col => col.field == resource.field && !col.hide)
definitionColumns.forEach((def) => {
if (def.field == resource.field) {
resourceColumns[index] = {...resource, ...def.column};
}
})
})
return {
columns: resourceColumns,
columnVisibilityModel: visibilityModel
}
}
export default CrudList;

View File

@@ -0,0 +1,146 @@
import React, { useContext } from "react";
import { JSONSchema7Definition } from "json-schema";
import {
FieldProps,
FormContextType,
getTemplate, getUiOptions,
RJSFSchema,
} from "@rjsf/utils";
import ArrayField from "@rjsf/core/lib/components/fields/ArrayField";
import validator from "@rjsf/validator-ajv8";
import Form from "@rjsf/mui";
import Typography from "@mui/material/Typography";
import { Box, Paper } from "@mui/material";
import { CrudTextRJSFSchema } from "../widgets/crud-text-widget";
import { ParametersContext } from "../../contexts/parameters-context";
export type CrudArrayFieldSchema = RJSFSchema & {
props? : any
}
const CrudArrayField = <T = any, S extends CrudArrayFieldSchema = CrudArrayFieldSchema, F extends FormContextType = any> (props: FieldProps<T[], S, F>)=> {
const { schema } = props
let isDictionary = false;
if (schema.props) {
if (schema.props.hasOwnProperty("display")) {
if (schema.props.display == "dictionary") {
isDictionary = true;
}
}
}
if (isDictionary) {
return <Dictionary {...props} />
}
return <ArrayField<T,S,F> {...props}/>
}
export default CrudArrayField;
type DictionaryEntry = {
key: string
value: string
};
const Dictionary = <
T = any,
S extends CrudTextRJSFSchema = CrudTextRJSFSchema,
F extends FormContextType = any
>(props: FieldProps<T[], S, F>)=> {
const { required, formData, onChange, registry, uiSchema, idSchema, schema } = props;
const uiOptions = getUiOptions<T[], S, F>(uiSchema);
const { parameters } = useContext(ParametersContext);
const ArrayFieldDescriptionTemplate = getTemplate<'ArrayFieldDescriptionTemplate', T[], S, F>(
'ArrayFieldDescriptionTemplate',
registry,
uiOptions
);
const ArrayFieldTitleTemplate = getTemplate<'ArrayFieldTitleTemplate', T[], S, F>(
'ArrayFieldTitleTemplate',
registry,
uiOptions
);
let properties = new Set<string>()
for (const field in parameters) {
for (const param of parameters[field]) {
properties.add(param)
}
}
let data: {[key:string]: string} = {}
if (formData !== undefined) {
for (const param of formData) {
// @ts-ignore
data[param.key] = param.value;
}
}
const emptyDict = Object.values(data).length == 0;
return (
<Paper elevation={2}>
<Box p={2}>
<ArrayFieldTitleTemplate
idSchema={idSchema}
title={uiOptions.title || schema.title}
schema={schema}
uiSchema={uiSchema}
required={required}
registry={registry}
/>
<ArrayFieldDescriptionTemplate
idSchema={idSchema}
description={uiOptions.description || schema.description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
{ emptyDict && <Typography>No variables found</Typography>}
{ !emptyDict && (
<Form
schema={getFormSchema(Array.from(properties.values()), required || false)}
tagName="div"
formData={data}
validator={validator}
omitExtraData={true}
onChange={(e, id) => {
console.log(e)
let value: T[] = []
for (const prop of properties) {
value.push({
key: prop,
value: e.formData.hasOwnProperty(prop) ? e.formData[prop] : undefined
} as T)
}
onChange(value)
}}
>
&nbsp;
</Form>
)}
</Box>
</Paper>
);
}
function getFormSchema(properties: string[], isRequired: boolean) {
const schema: JSONSchema7Definition = {
type: "object",
properties: {},
};
let required: string[] = []
for (const pname of properties) {
schema.properties![pname] = {
type: "string",
title: pname
}
if (isRequired) {
required.push(pname)
}
}
schema.required = required
return schema
}

View File

@@ -9,7 +9,6 @@ import {
FormContextType,
} from '@rjsf/utils';
import { CrudTextRJSFSchema } from "../widgets/crud-text-widget";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
/** The `ArrayFieldTemplate` component is the template used to render all items in an array.
@@ -45,7 +44,7 @@ export default function ArrayFieldTemplate<
} = registry.templates;
let gridSize = 12;
let numbered = false
let numbered = false;
if (schema.props) {
if (schema.props.hasOwnProperty("items_per_row")) {
gridSize = gridSize / schema.props.items_per_row;
@@ -74,7 +73,7 @@ export default function ArrayFieldTemplate<
registry={registry}
/>
<Grid2 container justifyContent='flex-start'>
{items &&
{ items &&
items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType<T, S, F>, index) => (
<Grid2 key={key} size={gridSize} >
<Grid2 container sx={{alignItems: "center"}} >
@@ -82,9 +81,10 @@ export default function ArrayFieldTemplate<
<Grid2 size={numbered ? 11.5 : 12} ><ArrayFieldItemTemplate key={key} {...itemProps} /></Grid2>
</Grid2>
</Grid2>
))}
))
}
</Grid2>
{canAdd && (
{ canAdd && (
<Grid2 container justifyContent='flex-end'>
<Grid2>
<Box mt={2}>

View File

@@ -1,6 +1,6 @@
import React from "react";
import { getDefaultRegistry } from "@rjsf/core";
import { FormContextType, getTemplate, RJSFSchema, WidgetProps } from "@rjsf/utils";
import { FormContextType, RJSFSchema, WidgetProps } from "@rjsf/utils";
import Typography from "@mui/material/Typography";
import ForeignKeyWidget from "./foreign-key";

View File

@@ -9,6 +9,7 @@ import React, { useState, useEffect, useContext, Fragment } from "react";
import { useForm, useList, useOne } from "@refinedev/core";
import { ResourceContext } from "../../contexts/ResourceContext";
import { CrudForm } from "../crud-form";
import { ParametersContext } from "../../contexts/parameters-context";
export type ForeignKeyReference = {
resource: string,
@@ -21,6 +22,7 @@ export type ForeignKeySchema = RJSFSchema & {
foreignKey?: {
reference: ForeignKeyReference
}
props? : any
}
export default function ForeignKeyWidget<T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
@@ -42,10 +44,10 @@ export default function ForeignKeyWidget<T = any, S extends ForeignKeySchema = F
const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
props: WidgetProps<T, S, F>
) => {
if (props.schema.foreignKey === undefined) {
const { onChange, label, fieldId, schema } = props;
if (schema.foreignKey === undefined) {
return;
}
const { onChange, label } = props
const [openFormModal, setOpenFormModal] = useState(false);
const [searchString, setSearchString] = useState<string>("");
@@ -55,12 +57,19 @@ const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema
return () => clearTimeout(handler);
}, [searchString]);
const { resource, schema, label: labelField = "label" } = props.schema.foreignKey.reference
const { setFieldParameters } = useContext(ParametersContext)
useEffect(() => {
if (schema.hasOwnProperty("props") && schema.props.hasOwnProperty("parametrized") && schema.props.parametrized) {
setFieldParameters(fieldId, [])
}
}, [])
const { resource, schema: fkSchema, label: labelField = "label" } = 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 }],
filters: [{ field: "search", operator: "contains", value: debouncedInputValue }],
sorters: [{ field: "label", order: "asc" }],
});
@@ -104,13 +113,13 @@ const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema
>
<DialogContent>
<FormContainerNew
schemaName={schema}
schemaName={fkSchema}
resourceBasePath={basePath}
resource={resource}
uiSchema={{}}
onSuccess={(data: any) => {
setOpenFormModal(false)
onChange(data.data.id);
onChange(data.id);
}}
/>
</DialogContent>
@@ -122,28 +131,37 @@ const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema
const ChosenValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
props: WidgetProps<T, S, F> & { onClear: () => void }
) => {
const { onClear, value } = props;
const { onClear, value, schema, id: fieldId } = props;
const [openFormModal, setOpenFormModal] = React.useState(false);
if (props.schema.foreignKey === undefined) {
return;
}
const { resource, schema, label: labelField = "label", displayedFields } = props.schema.foreignKey.reference
const { resource, schema: fkSchema, label: labelField = "label", displayedFields } = props.schema.foreignKey.reference
const { basePath } = useContext(ResourceContext)
const { data, isLoading } = useOne({
const { data, isLoading, isSuccess } = useOne({
resource: `${basePath}/${resource}`,
id: value
});
const { setFieldParameters } = useContext(ParametersContext)
useEffect(() => {
if (isSuccess && schema.hasOwnProperty("props") && schema.props.hasOwnProperty("parametrized") && schema.props.parametrized) {
const record = data.data;
setFieldParameters(fieldId, extractParameters(record))
}
}, [isSuccess])
if (isLoading || data === undefined) {
return <CircularProgress />
}
const record = data.data;
return (
<>
<TextField label={ props.label } variant="outlined" disabled={true} value={data.data[labelField]}
<TextField label={ props.label } variant="outlined" disabled={true} value={record[labelField]}
slotProps={{
input: {
endAdornment: (
@@ -158,7 +176,7 @@ const ChosenValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F e
},
}}
/>
{ displayedFields && <Preview id={value} basePath={basePath} resource={resource} displayedFields={displayedFields}/>}
{ displayedFields && <Preview record={record} displayedFields={displayedFields}/>}
<Modal
open={openFormModal}
onClose={() => setOpenFormModal(false)}
@@ -167,7 +185,7 @@ const ChosenValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F e
>
<DialogContent>
<FormContainerEdit
schemaName={schema}
schemaName={fkSchema}
resourceBasePath={basePath}
resource={resource}
uiSchema={{}}
@@ -233,7 +251,8 @@ const FormContainerNew = (props: FormContainerProps) => {
const { schemaName, resourceBasePath, resource, uiSchema = {}, onSuccess } = props;
const { onFinish } = useForm({
resource: `${resourceBasePath}/${resource}`,
action: "create"
action: "create",
onMutationSuccess: data => onSuccess(data.data)
});
return (
@@ -242,25 +261,14 @@ const FormContainerNew = (props: FormContainerProps) => {
schemaName={schemaName}
uiSchema={uiSchema}
resourceBasePath={resourceBasePath}
onSubmit={(data:any) => {
onFinish(data);
onSuccess(data);
}} />
onSubmit={(data:any) => { onFinish(data);}}
/>
</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 />
}
const Preview = (props: {record: any, displayedFields: [string]}) => {
const { record, displayedFields } = props
return (
<Grid2 container spacing={2}>
@@ -268,10 +276,29 @@ const Preview = (props: {id: string, resource: string, basePath: string, display
return (
<Fragment key={index}>
<Grid2 size={2}><Container>{field}</Container></Grid2>
<Grid2 size={9}><Container dangerouslySetInnerHTML={{ __html: data.data[field] }} ></Container></Grid2>
<Grid2 size={9}><Container dangerouslySetInnerHTML={{ __html: record[field] }} ></Container></Grid2>
</Fragment>
)
})}
</Grid2>
);
}
const extractParameters = (obj: any)=> {
let result: string[] = [];
for (const [k, v] of Object.entries(obj)) {
if (typeof(obj[k]) == "string") {
const matches = obj[k].match(/%[^\s.]+%/g);
if (matches) {
const filtered = matches.map((p: string | any[]) => p.slice(1,-1)) as string[]
result = result.concat(filtered);
}
} else if (typeof(obj[k]) == "object") {
if (obj[k]) {
result = result.concat(extractParameters(obj[k]));
}
}
}
return result
}

View File

@@ -1,4 +1,4 @@
import React, { createContext, PropsWithChildren } from 'react';
import { createContext } from 'react';
type ResourceContextType = {
basePath: string,

View File

@@ -0,0 +1,30 @@
import React, { createContext, PropsWithChildren, useState } from 'react';
type Parameters = {
[field: string]: string[]
}
type ParametersContextType = {
parameters: Parameters,
setFieldParameters: (fieldName: string, parameterList: string[]) => void
}
export const ParametersContext = createContext<ParametersContextType>(
{} as ParametersContextType
);
export const ParametersContextProvider: React.FC<PropsWithChildren> = ({ children }: PropsWithChildren) => {
const [parameters, setParameters] = useState<Parameters>({});
function setFieldParameters(fieldName: string, parameterList: string[]) {
let params = structuredClone(parameters)
params[fieldName] = parameterList
setParameters(params);
}
return (
<ParametersContext.Provider value={{ parameters, setFieldParameters }} >
{children}
</ParametersContext.Provider>
);
}

View File

@@ -0,0 +1,109 @@
import { useEffect, useState } from "react";
import { jsonschemaProvider } from "../providers/jsonschema-provider";
import { GridColDef, GridColumnVisibilityModel, GridValidRowModel } from "@mui/x-data-grid";
type ResourceSchemaType = "create" | "update" | "card";
export function useResourceSchema(schemaName: string, type: ResourceSchemaType) {
const [schema, setSchema] = useState({});
const [schemaLoading, setSchemaLoading] = useState(true);
useEffect(() => {
const fetchSchema = async () => {
try {
let resourceSchema
if (type == "create") {
resourceSchema = await jsonschemaProvider.getCreateResourceSchema(schemaName);
} else if (type == "update") {
resourceSchema = await jsonschemaProvider.getCardResourceSchema(schemaName);
} else {
resourceSchema = await jsonschemaProvider.getUpdateResourceSchema(schemaName);
}
setSchema(resourceSchema);
setSchemaLoading(false);
} catch (error) {
console.error(`Error while retrieving schema: ${schemaName} `, error);
setSchemaLoading(false);
}
};
fetchSchema();
}, []);
return { schema, schemaLoading }
}
type ColumnSchema<T extends GridValidRowModel> = {
columns: GridColDef<T>[],
columnVisibilityModel: GridColumnVisibilityModel
}
type ColumnDefinition = {
field: string,
column: Partial<GridColDef>,
hide?: boolean
}
export function useResourceColumns<T extends GridValidRowModel>(schemaName: string, columnDefinitions: ColumnDefinition[]) {
const [columnSchema, setColumnSchema] = useState<ColumnSchema<T>>()
const [columnLoading, setColumnLoading] = useState(true);
useEffect(() => {
const fetchSchema = async () => {
try {
const resourceColumns = await jsonschemaProvider.getReadResourceColumns(schemaName)
const definedColumns = computeColumnSchema<T>(columnDefinitions, resourceColumns)
setColumnSchema(definedColumns);
setColumnLoading(false);
} catch (error) {
console.error('Error while retrieving columns schema:', error);
setColumnLoading(false);
}
};
fetchSchema();
}, []);
return { columnSchema, columnLoading }
}
function computeColumnSchema<T extends GridValidRowModel>(definitionColumns: ColumnDefinition[], resourceColumns: GridColDef[]): ColumnSchema<T> {
//reorder resourceColumns as in definition
definitionColumns.slice().reverse().forEach(first => {
resourceColumns.sort(function(x,y){ return x.field == first.field ? -1 : y.field == first.field ? 1 : 0; });
})
let visibilityModel: GridColumnVisibilityModel = {}
resourceColumns.forEach((resource, index) =>{
visibilityModel[resource.field] = definitionColumns.some(col => col.field == resource.field && !col.hide)
definitionColumns.forEach((def) => {
if (def.field == resource.field) {
resourceColumns[index] = {...resource, ...def.column};
}
})
})
return {
columns: resourceColumns,
columnVisibilityModel: visibilityModel
}
}
export function useResourceFilter(resourceName: string, resourcePath: string) {
const [hasSearch, setHasSearch] = useState(false)
const [filtersSchema, setFiltersSchema] = useState<any[]>([])
const [filtersLoading, setFiltersLoading] = useState(true);
useEffect(() => {
const fetchSchema = async () => {
try {
setHasSearch(await jsonschemaProvider.hasSearch(resourcePath))
const resourceFilters = await jsonschemaProvider.getListFilters(resourceName, resourcePath)
setFiltersSchema(resourceFilters);
setFiltersLoading(false);
} catch (error) {
console.error('Error while retrieving filter schema:', error);
setFiltersLoading(false);
}
};
fetchSchema();
}, []);
return { hasSearch, filtersSchema, filtersLoading }
}

View File

@@ -170,6 +170,8 @@ const getFieldFilterType = (fieldName: string, field: RJSFSchema): string => {
enumValues.push(f.const)
}
return `enum(${enumValues.join("|")})`
} else if (is_enum(field) && field.enum != undefined) {
return `enum(${field.enum.join("|")})`
} else if (field.hasOwnProperty('type')) {
if (field.type == "string" && field.format == "date-time") {
return "dateTime";
@@ -193,6 +195,13 @@ function buildColumns (rawSchemas: RJSFSchema, resourceName: string, prefix: str
let resource = structuredClone(jst.getResource(resourceName));
let result: GridColDef[] = [];
if (is_enum(resource) && prefix !== undefined) {
return [{
field: prefix,
headerName: i18n.t(`schemas.${shortResourceName}.${convertCamelToSnake(prefix)}`) as string,
type: "string"
}];
}
for (const prop_name in resource.properties) {
let prop = resource.properties[prop_name];
@@ -469,7 +478,12 @@ const JsonSchemaTraverser = class {
if (is_array(resource) && property_name == 'items') {
return resource.items as RJSFSchema;
} else if (is_object(resource) && resource.properties !== undefined && property_name in resource.properties!) {
return resource.properties[property_name] as RJSFSchema;
const prop = resource.properties[property_name];
if (is_reference(prop)) {
const subresourceName = get_reference_name(prop);
return this.getResource(subresourceName);
}
return prop as RJSFSchema;
} else if (is_reference(resource)) {
let subresourceName = get_reference_name(resource);
let subresource = this.getResource(subresourceName);

View File

@@ -2,7 +2,7 @@ import TextField from "@mui/material/TextField";
import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
import Autocomplete from "@mui/material/Autocomplete";
import { FirmContext } from "../../../contexts/FirmContext";
import { useContext } from "react";
import { Fragment, useContext } from "react";
import { Box, Grid2, styled } from "@mui/material";
import Stack from "@mui/material/Stack";
import dayjs from "dayjs";
@@ -25,9 +25,9 @@ const FilterForm = (props: FilterFormProps) => {
let currentValue = values
const formField = fields.filter(f => f.name != "search").map((f, index) =>
<>
{ f.name == "created_at" && <Box width="100%" key={`created_at_break-${index}`} /> }
<Grid2 size={6} key={`${f.name}-${index}`} >
<Fragment key={`${f.name}-${index}`} >
{ f.name == "created_at" && <Box width="100%" /> }
<Grid2 size={6}>
<FilterFormField
field={f}
value={values.hasOwnProperty(f.name) ? values[f.name] : {}}
@@ -53,7 +53,7 @@ const FilterForm = (props: FilterFormProps) => {
}}
/>
</Grid2>
</>
</Fragment>
);
return (

View File

@@ -25,6 +25,8 @@ export const ContractRoutes = () => {
const ListContract = () => {
const columns = [
{ field: "label", column: { flex: 1 }},
{ field: "status", column: { width: 160 }},
{ field: "updated_at", column: { width: 160 }},
];
return <List<Contract> resource={`contracts`} schemaName={"Contract"} columns={columns} />
}

View File

@@ -29,7 +29,9 @@ export const DraftRoutes = () => {
const ListDraft = () => {
const columns = [
{ field: "label", column: { flex: 1 }},
{ field: "label", column: { flex: 1 }},
{ field: "status", column: { width: 160 }},
{ field: "updated_at", column: { width: 160 }},
];
return <List<Draft> resource={`contracts/drafts`} columns={columns} schemaName={"Contract"} />
}

View File

@@ -22,6 +22,7 @@ export const ProvisionRoutes = () => {
const ListProvision = () => {
const columns = [
{ field: "label", column: { flex: 1 }},
{ field: "updated_at", column: { width: 160 }},
];
return <List<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplate"} columns={columns} />
}

View File

@@ -21,6 +21,7 @@ export const TemplateRoutes = () => {
const ListTemplate = () => {
const columns = [
{ field: "label", column: { flex: 1 }},
{ field: "updated_at", column: { width: 160 }},
];
return <List<Template> resource={`templates/contracts`} schemaName={"ContractTemplate"} columns={columns} />
}