Compare commits
9 Commits
9aac1d3e34
...
fix/firm_i
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ff15cdef4 | |||
| 72b6f26ebc | |||
| b06ce4eefd | |||
| 49317e905b | |||
| 189c896e60 | |||
| 4ae0b321b9 | |||
| 5a3b87e82c | |||
| 0731ac3b6e | |||
| 77fa4cde35 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
|
||||
146
gui/rpk-gui/src/lib/crud/components/fields/crud-array-field.tsx
Normal file
146
gui/rpk-gui/src/lib/crud/components/fields/crud-array-field.tsx
Normal 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)
|
||||
}}
|
||||
>
|
||||
|
||||
</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
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { createContext, PropsWithChildren } from 'react';
|
||||
import { createContext } from 'react';
|
||||
|
||||
type ResourceContextType = {
|
||||
basePath: string,
|
||||
|
||||
30
gui/rpk-gui/src/lib/crud/contexts/parameters-context.tsx
Normal file
30
gui/rpk-gui/src/lib/crud/contexts/parameters-context.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
109
gui/rpk-gui/src/lib/crud/hook/index.tsx
Normal file
109
gui/rpk-gui/src/lib/crud/hook/index.tsx
Normal 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 }
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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"} />
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user