Compare commits
9 Commits
0731ac3b6e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d2bea9180a | |||
| f0b241f37d | |||
| f76f4d5673 | |||
| 72b6f26ebc | |||
| b06ce4eefd | |||
| 49317e905b | |||
| 189c896e60 | |||
| 4ae0b321b9 | |||
| 5a3b87e82c |
@@ -3,6 +3,7 @@ import os
|
|||||||
import base64
|
import base64
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from beanie import PydanticObjectId
|
||||||
from fastapi import APIRouter, HTTPException, Request, Depends
|
from fastapi import APIRouter, HTTPException, Request, Depends
|
||||||
from fastapi.responses import HTMLResponse, FileResponse
|
from fastapi.responses import HTMLResponse, FileResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
@@ -18,24 +19,22 @@ from firm.template.models import ProvisionTemplate
|
|||||||
from firm.contract.models import ContractDraft, Contract, ContractStatus, replace_variables_in_value
|
from firm.contract.models import ContractDraft, Contract, ContractStatus, replace_variables_in_value
|
||||||
|
|
||||||
|
|
||||||
async def build_model(model):
|
async def build_model(db, model):
|
||||||
parties = []
|
parties = []
|
||||||
for p in model.parties:
|
for p in model.parties:
|
||||||
party = {
|
party = {
|
||||||
"entity": await Entity.get(p.entity_id),
|
"entity": await Entity.get(db, p.entity_id),
|
||||||
"part": p.part
|
"part": p.part
|
||||||
}
|
}
|
||||||
if p.representative_id:
|
if p.representative_id:
|
||||||
party['representative'] = await Entity.get(p.representative_id)
|
party['representative'] = await Entity.get(db, p.representative_id)
|
||||||
|
|
||||||
parties.append(party)
|
parties.append(party)
|
||||||
|
|
||||||
model.parties = parties
|
|
||||||
|
|
||||||
provisions = []
|
provisions = []
|
||||||
for p in model.provisions:
|
for p in model.provisions:
|
||||||
if p.provision.type == "template":
|
if p.provision.type == "template":
|
||||||
provision = await ProvisionTemplate.get(p.provision.provision_template_id)
|
provision = await ProvisionTemplate.get(db, p.provision.provision_template_id)
|
||||||
else:
|
else:
|
||||||
provision = p.provision
|
provision = p.provision
|
||||||
|
|
||||||
@@ -43,16 +42,16 @@ async def build_model(model):
|
|||||||
provision.body = replace_variables_in_value(model.variables, provision.body)
|
provision.body = replace_variables_in_value(model.variables, provision.body)
|
||||||
provisions.append(provision)
|
provisions.append(provision)
|
||||||
|
|
||||||
model.provisions = provisions
|
model_dict = model.dict()
|
||||||
|
model_dict['parties'] = parties
|
||||||
model = model.dict()
|
model_dict['provisions'] = provisions
|
||||||
model['location'] = "Los Santos, SA"
|
model_dict['location'] = "Los Santos, SA"
|
||||||
model['date'] = datetime.date(1970, 1, 1)
|
model_dict['date'] = datetime.date(1970, 1, 1)
|
||||||
model['lawyer'] = {'entity_data': {
|
model_dict['lawyer'] = {'entity_data': {
|
||||||
"firstname": "prénom avocat",
|
"firstname": "prénom avocat",
|
||||||
"lastname": "nom avocat",
|
"lastname": "nom avocat",
|
||||||
}}
|
}}
|
||||||
return model
|
return model_dict
|
||||||
|
|
||||||
|
|
||||||
BASE_PATH = Path(__file__).resolve().parent
|
BASE_PATH = Path(__file__).resolve().parent
|
||||||
@@ -87,8 +86,14 @@ def retrieve_signature_png(filepath):
|
|||||||
|
|
||||||
preview_router = APIRouter()
|
preview_router = APIRouter()
|
||||||
@preview_router.get("/draft/{draft_id}", response_class=HTMLResponse, tags=["Contract Draft"])
|
@preview_router.get("/draft/{draft_id}", response_class=HTMLResponse, tags=["Contract Draft"])
|
||||||
async def preview_draft(draft_id: str, reg=Depends(get_tenant_registry)) -> str:
|
async def preview_draft(draft_id: PydanticObjectId, reg=Depends(get_tenant_registry)) -> str:
|
||||||
draft = await build_model(await ContractDraft.get(reg.db, draft_id))
|
record = await ContractDraft.get(reg.db, draft_id)
|
||||||
|
if not record:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"Contract Draft record not found!"
|
||||||
|
)
|
||||||
|
draft = await build_model(reg.db, record)
|
||||||
|
|
||||||
return await render_print('', draft)
|
return await render_print('', draft)
|
||||||
|
|
||||||
@@ -104,7 +109,7 @@ async def preview_contract_by_signature(signature_id: UUID, reg=Depends(get_tena
|
|||||||
|
|
||||||
|
|
||||||
@preview_router.get("/{contract_id}", response_class=HTMLResponse, tags=["Contract"])
|
@preview_router.get("/{contract_id}", response_class=HTMLResponse, tags=["Contract"])
|
||||||
async def preview_contract(contract_id: str, reg=Depends(get_tenant_registry)) -> str:
|
async def preview_contract(contract_id: PydanticObjectId, reg=Depends(get_tenant_registry)) -> str:
|
||||||
contract = await Contract.get(reg.db, contract_id)
|
contract = await Contract.get(reg.db, contract_id)
|
||||||
for p in contract.parties:
|
for p in contract.parties:
|
||||||
if p.signature_affixed:
|
if p.signature_affixed:
|
||||||
@@ -115,7 +120,7 @@ async def preview_contract(contract_id: str, reg=Depends(get_tenant_registry)) -
|
|||||||
|
|
||||||
print_router = APIRouter()
|
print_router = APIRouter()
|
||||||
@print_router.get("/pdf/{contract_id}", response_class=FileResponse, tags=["Contract"])
|
@print_router.get("/pdf/{contract_id}", response_class=FileResponse, tags=["Contract"])
|
||||||
async def create_pdf(contract_id: str, reg=Depends(get_tenant_registry)) -> str:
|
async def create_pdf(contract_id: PydanticObjectId, reg=Depends(get_tenant_registry)) -> str:
|
||||||
contract = await Contract.get(reg.db, contract_id)
|
contract = await Contract.get(reg.db, contract_id)
|
||||||
contract_path = "media/contracts/{}.pdf".format(contract_id)
|
contract_path = "media/contracts/{}.pdf".format(contract_id)
|
||||||
if not os.path.isfile(contract_path):
|
if not os.path.isfile(contract_path):
|
||||||
@@ -142,7 +147,7 @@ async def create_pdf(contract_id: str, reg=Depends(get_tenant_registry)) -> str:
|
|||||||
|
|
||||||
|
|
||||||
@print_router.get("/opengraph/{signature_id}", response_class=HTMLResponse, tags=["Signature"])
|
@print_router.get("/opengraph/{signature_id}", response_class=HTMLResponse, tags=["Signature"])
|
||||||
async def get_signature_opengraph(signature_id: str, request: Request, reg=Depends(get_tenant_registry)) -> str:
|
async def get_signature_opengraph(signature_id: UUID, request: Request, reg=Depends(get_tenant_registry)) -> str:
|
||||||
contract = await Contract.find_by_signature_id(reg.db, signature_id)
|
contract = await Contract.find_by_signature_id(reg.db, signature_id)
|
||||||
signature = contract.get_signature(signature_id)
|
signature = contract.get_signature(signature_id)
|
||||||
template = templates.get_template("opengraph.html")
|
template = templates.get_template("opengraph.html")
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<hr/>
|
<hr/>
|
||||||
<p>À {{ contract.location }} le {{ contract.date.strftime('%d/%m/%Y') }}</p>
|
<p>À {{ contract.location }} le {{ contract.date.strftime('%d/%m/%Y') }}</p>
|
||||||
<p class="mention">(Signatures précédée de la mention « Lu et approuvé »)</p>
|
<p class="mention">(Signatures précédées de la mention « Lu et approuvé »)</p>
|
||||||
<table class="signatures">
|
<table class="signatures">
|
||||||
<tr>
|
<tr>
|
||||||
{% for party in contract.parties %}
|
{% for party in contract.parties %}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ProvisionTemplateReference(BaseModel):
|
|||||||
|
|
||||||
provision_template_id: PydanticObjectId = ForeignKey(
|
provision_template_id: PydanticObjectId = ForeignKey(
|
||||||
"templates/provisions",
|
"templates/provisions",
|
||||||
"TemplateProvision",
|
"ProvisionTemplate",
|
||||||
['title', 'body'],
|
['title', 'body'],
|
||||||
props={"parametrized": True},
|
props={"parametrized": True},
|
||||||
title="Template de clause"
|
title="Template de clause"
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import ArrayFieldTemplate from "./templates/ArrayFieldTemplate"
|
|||||||
import ArrayFieldItemTemplate from "./templates/ArrayFieldItemTemplate";
|
import ArrayFieldItemTemplate from "./templates/ArrayFieldItemTemplate";
|
||||||
import { ResourceContext } from "../contexts/ResourceContext";
|
import { ResourceContext } from "../contexts/ResourceContext";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
import { ParametersContextProvider } from "../contexts/parameters-context";
|
||||||
|
import CrudArrayField from "./fields/crud-array-field";
|
||||||
|
|
||||||
type BaseFormProps = {
|
type BaseFormProps = {
|
||||||
schema: RJSFSchema,
|
schema: RJSFSchema,
|
||||||
@@ -19,11 +21,12 @@ type BaseFormProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const customWidgets: RegistryWidgetsType = {
|
export const customWidgets: RegistryWidgetsType = {
|
||||||
TextWidget: CrudTextWidget
|
TextWidget: CrudTextWidget,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const customFields: RegistryFieldsType = {
|
export const customFields: RegistryFieldsType = {
|
||||||
AnyOfField: UnionEnumField
|
AnyOfField: UnionEnumField,
|
||||||
|
ArrayField: CrudArrayField
|
||||||
}
|
}
|
||||||
|
|
||||||
const customTemplates = {
|
const customTemplates = {
|
||||||
@@ -36,6 +39,7 @@ export const BaseForm: React.FC<BaseFormProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ResourceContext.Provider value={{basePath: resourceBasePath}} >
|
<ResourceContext.Provider value={{basePath: resourceBasePath}} >
|
||||||
|
<ParametersContextProvider>
|
||||||
<Form
|
<Form
|
||||||
schema={schema}
|
schema={schema}
|
||||||
uiSchema={uiSchema === undefined ? {} : uiSchema}
|
uiSchema={uiSchema === undefined ? {} : uiSchema}
|
||||||
@@ -49,6 +53,7 @@ export const BaseForm: React.FC<BaseFormProps> = (props) => {
|
|||||||
onChange={(e, id) => onChange != undefined && onChange(e.formData)}
|
onChange={(e, id) => onChange != undefined && onChange(e.formData)}
|
||||||
children={children}
|
children={children}
|
||||||
/>
|
/>
|
||||||
|
</ParametersContextProvider>
|
||||||
</ResourceContext.Provider>
|
</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 { Accordion, AccordionDetails, AccordionSummary, CircularProgress } from "@mui/material";
|
||||||
import FilterForm from "../../filter-form/components/filter-form";
|
import FilterForm from "../../filter-form/components/filter-form";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { useSearchParams } from "react-router";
|
|
||||||
import { GridExpandMoreIcon } from "@mui/x-data-grid";
|
import { GridExpandMoreIcon } from "@mui/x-data-grid";
|
||||||
|
import { useResourceFilter } from "../hook";
|
||||||
|
|
||||||
export type OnChangeValue = {
|
export type OnChangeValue = {
|
||||||
search: string | null
|
search: string | null
|
||||||
@@ -19,24 +17,7 @@ type CrudFiltersProps = {
|
|||||||
|
|
||||||
const CrudFilters = (props: CrudFiltersProps) => {
|
const CrudFilters = (props: CrudFiltersProps) => {
|
||||||
const { resourceName, resourcePath, onChange } = props
|
const { resourceName, resourcePath, onChange } = props
|
||||||
|
const { hasSearch, filtersSchema, filtersLoading } = useResourceFilter(resourceName, resourcePath)
|
||||||
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 fetching data:', error);
|
|
||||||
setFiltersLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchSchema();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (filtersLoading) {
|
if (filtersLoading) {
|
||||||
return <CircularProgress />
|
return <CircularProgress />
|
||||||
@@ -81,8 +62,6 @@ type SearchFilter = {
|
|||||||
|
|
||||||
const SearchFilter = (props: SearchFilter) => {
|
const SearchFilter = (props: SearchFilter) => {
|
||||||
const {value, onChange} = props;
|
const {value, onChange} = props;
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
label="schemas.search"
|
label="schemas.search"
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { ReactNode, useEffect, useState } from "react";
|
import { ReactNode } from "react";
|
||||||
import { CircularProgress } from "@mui/material";
|
import { CircularProgress } from "@mui/material";
|
||||||
import { useForm } from "@refinedev/core";
|
|
||||||
import { UiSchema } from "@rjsf/utils";
|
import { UiSchema } from "@rjsf/utils";
|
||||||
import { jsonschemaProvider } from "../providers/jsonschema-provider";
|
|
||||||
import { BaseForm } from "./base-form";
|
import { BaseForm } from "./base-form";
|
||||||
|
import { useResourceSchema } from "../hook";
|
||||||
|
|
||||||
type CrudFormProps = {
|
type CrudFormProps = {
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
@@ -18,31 +17,8 @@ type CrudFormProps = {
|
|||||||
|
|
||||||
export const CrudForm: React.FC<CrudFormProps> = (props) => {
|
export const CrudForm: React.FC<CrudFormProps> = (props) => {
|
||||||
const { schemaName, uiSchema, record, resourceBasePath, defaultValue, children, onSubmit=(data: any) => {}, card=false } = props;
|
const { schemaName, uiSchema, record, resourceBasePath, defaultValue, children, onSubmit=(data: any) => {}, card=false } = props;
|
||||||
|
const type = record === undefined ? "create" : card ? "card" : "update"
|
||||||
const [schema, setSchema] = useState({});
|
const { schema, schemaLoading } = useResourceSchema(schemaName, type);
|
||||||
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();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if(schemaLoading) {
|
if(schemaLoading) {
|
||||||
return <CircularProgress />
|
return <CircularProgress />
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
import { DataGrid, GridColDef, GridColumnVisibilityModel, GridValidRowModel } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef, GridColumnVisibilityModel, GridValidRowModel } from "@mui/x-data-grid";
|
||||||
import { UiSchema } from "@rjsf/utils";
|
import { UiSchema } from "@rjsf/utils";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useResourceColumns } from "../hook";
|
||||||
import { jsonschemaProvider } from "../providers/jsonschema-provider";
|
|
||||||
import { CircularProgress } from "@mui/material";
|
|
||||||
|
|
||||||
type CrudListProps = {
|
type CrudListProps = {
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
@@ -32,24 +31,9 @@ const CrudList = <T extends GridValidRowModel>(props: CrudListProps) => {
|
|||||||
columnDefinitions
|
columnDefinitions
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [columnSchema, setColumnSchema] = useState<ColumnSchema<T>>()
|
const { columnSchema, columnLoading } = useResourceColumns<T>(schemaName, columnDefinitions);
|
||||||
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();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (schemaLoading || columnSchema === undefined) {
|
if (columnLoading || columnSchema === undefined) {
|
||||||
return <CircularProgress />
|
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;
|
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,
|
FormContextType,
|
||||||
} from '@rjsf/utils';
|
} from '@rjsf/utils';
|
||||||
import { CrudTextRJSFSchema } from "../widgets/crud-text-widget";
|
import { CrudTextRJSFSchema } from "../widgets/crud-text-widget";
|
||||||
import Stack from "@mui/material/Stack";
|
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
/** The `ArrayFieldTemplate` component is the template used to render all items in an array.
|
/** The `ArrayFieldTemplate` component is the template used to render all items in an array.
|
||||||
@@ -45,7 +44,7 @@ export default function ArrayFieldTemplate<
|
|||||||
} = registry.templates;
|
} = registry.templates;
|
||||||
|
|
||||||
let gridSize = 12;
|
let gridSize = 12;
|
||||||
let numbered = false
|
let numbered = false;
|
||||||
if (schema.props) {
|
if (schema.props) {
|
||||||
if (schema.props.hasOwnProperty("items_per_row")) {
|
if (schema.props.hasOwnProperty("items_per_row")) {
|
||||||
gridSize = gridSize / schema.props.items_per_row;
|
gridSize = gridSize / schema.props.items_per_row;
|
||||||
@@ -82,7 +81,8 @@ export default function ArrayFieldTemplate<
|
|||||||
<Grid2 size={numbered ? 11.5 : 12} ><ArrayFieldItemTemplate key={key} {...itemProps} /></Grid2>
|
<Grid2 size={numbered ? 11.5 : 12} ><ArrayFieldItemTemplate key={key} {...itemProps} /></Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
))}
|
))
|
||||||
|
}
|
||||||
</Grid2>
|
</Grid2>
|
||||||
{ canAdd && (
|
{ canAdd && (
|
||||||
<Grid2 container justifyContent='flex-end'>
|
<Grid2 container justifyContent='flex-end'>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getDefaultRegistry } from "@rjsf/core";
|
import { getDefaultRegistry } from "@rjsf/core";
|
||||||
import { FormContextType, getTemplate, RJSFSchema, WidgetProps } from "@rjsf/utils";
|
import { FormContextType, RJSFSchema, WidgetProps } from "@rjsf/utils";
|
||||||
|
import { Button, InputAdornment } from "@mui/material";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
import CopyAllIcon from '@mui/icons-material/CopyAll';
|
||||||
|
|
||||||
import ForeignKeyWidget from "./foreign-key";
|
import ForeignKeyWidget from "./foreign-key";
|
||||||
import RichtextWidget from "./richtext";
|
import RichtextWidget from "./richtext";
|
||||||
@@ -18,8 +21,37 @@ export default function CrudTextWidget<T = any, S extends CrudTextRJSFSchema = C
|
|||||||
return <Typography >{schema.const as string}</Typography>;
|
return <Typography >{schema.const as string}</Typography>;
|
||||||
} else if (schema.props?.hasOwnProperty("richtext")) {
|
} else if (schema.props?.hasOwnProperty("richtext")) {
|
||||||
return <RichtextWidget {...props} />;
|
return <RichtextWidget {...props} />;
|
||||||
|
} else if (schema.props?.hasOwnProperty("display") && schema.props.display == "signature-link") {
|
||||||
|
return <SignatureLink {...props} />
|
||||||
} else {
|
} else {
|
||||||
const { widgets: { TextWidget } } = getDefaultRegistry<T,S,F>();
|
const { widgets: { TextWidget } } = getDefaultRegistry<T,S,F>();
|
||||||
return <TextWidget {...props} />;
|
return <TextWidget {...props} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SignatureLink = <T = any, S extends CrudTextRJSFSchema = CrudTextRJSFSchema, F extends FormContextType = any>(props: WidgetProps<T, S, F> )=> {
|
||||||
|
const { label, value } = props;
|
||||||
|
const basePath = "/contracts/signature/";
|
||||||
|
const url = location.origin + basePath + value
|
||||||
|
|
||||||
|
return <TextField
|
||||||
|
label={ label }
|
||||||
|
variant="outlined"
|
||||||
|
disabled={true}
|
||||||
|
value={url}
|
||||||
|
slotProps={{
|
||||||
|
input: {
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => navigator.clipboard.writeText(url)}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<CopyAllIcon />
|
||||||
|
</Button>
|
||||||
|
</InputAdornment>),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import React, { useState, useEffect, useContext, Fragment } from "react";
|
|||||||
import { useForm, useList, useOne } from "@refinedev/core";
|
import { useForm, useList, useOne } from "@refinedev/core";
|
||||||
import { ResourceContext } from "../../contexts/ResourceContext";
|
import { ResourceContext } from "../../contexts/ResourceContext";
|
||||||
import { CrudForm } from "../crud-form";
|
import { CrudForm } from "../crud-form";
|
||||||
|
import { ParametersContext } from "../../contexts/parameters-context";
|
||||||
|
|
||||||
export type ForeignKeyReference = {
|
export type ForeignKeyReference = {
|
||||||
resource: string,
|
resource: string,
|
||||||
@@ -21,6 +22,7 @@ export type ForeignKeySchema = RJSFSchema & {
|
|||||||
foreignKey?: {
|
foreignKey?: {
|
||||||
reference: ForeignKeyReference
|
reference: ForeignKeyReference
|
||||||
}
|
}
|
||||||
|
props? : any
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ForeignKeyWidget<T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = 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>(
|
const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F extends FormContextType = any>(
|
||||||
props: WidgetProps<T, S, F>
|
props: WidgetProps<T, S, F>
|
||||||
) => {
|
) => {
|
||||||
if (props.schema.foreignKey === undefined) {
|
const { onChange, label, fieldId, schema } = props;
|
||||||
|
if (schema.foreignKey === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { onChange, label } = props
|
|
||||||
|
|
||||||
const [openFormModal, setOpenFormModal] = useState(false);
|
const [openFormModal, setOpenFormModal] = useState(false);
|
||||||
const [searchString, setSearchString] = useState<string>("");
|
const [searchString, setSearchString] = useState<string>("");
|
||||||
@@ -55,12 +57,19 @@ const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema
|
|||||||
return () => clearTimeout(handler);
|
return () => clearTimeout(handler);
|
||||||
}, [searchString]);
|
}, [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 { basePath } = useContext(ResourceContext)
|
||||||
const { data, isLoading } = useList({
|
const { data, isLoading } = useList({
|
||||||
resource: `${basePath}/${resource}`,
|
resource: `${basePath}/${resource}`,
|
||||||
pagination: { current: 1, pageSize: 10, mode: "server" },
|
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" }],
|
sorters: [{ field: "label", order: "asc" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,13 +113,13 @@ const RealAutocomplete = <T = any, S extends ForeignKeySchema = ForeignKeySchema
|
|||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<FormContainerNew
|
<FormContainerNew
|
||||||
schemaName={schema}
|
schemaName={fkSchema}
|
||||||
resourceBasePath={basePath}
|
resourceBasePath={basePath}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
uiSchema={{}}
|
uiSchema={{}}
|
||||||
onSuccess={(data: any) => {
|
onSuccess={(data: any) => {
|
||||||
setOpenFormModal(false)
|
setOpenFormModal(false)
|
||||||
onChange(data.data.id);
|
onChange(data.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</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>(
|
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, schema, id: fieldId } = props;
|
||||||
|
|
||||||
const [openFormModal, setOpenFormModal] = React.useState(false);
|
const [openFormModal, setOpenFormModal] = React.useState(false);
|
||||||
|
|
||||||
if (props.schema.foreignKey === undefined) {
|
if (props.schema.foreignKey === undefined) {
|
||||||
return;
|
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 { basePath } = useContext(ResourceContext)
|
||||||
|
|
||||||
const { data, isLoading } = useOne({
|
const { data, isLoading, isSuccess } = useOne({
|
||||||
resource: `${basePath}/${resource}`,
|
resource: `${basePath}/${resource}`,
|
||||||
id: value
|
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) {
|
if (isLoading || data === undefined) {
|
||||||
return <CircularProgress />
|
return <CircularProgress />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const record = data.data;
|
||||||
return (
|
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={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
endAdornment: (
|
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
|
<Modal
|
||||||
open={openFormModal}
|
open={openFormModal}
|
||||||
onClose={() => setOpenFormModal(false)}
|
onClose={() => setOpenFormModal(false)}
|
||||||
@@ -167,7 +185,7 @@ const ChosenValue = <T = any, S extends ForeignKeySchema = ForeignKeySchema, F e
|
|||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<FormContainerEdit
|
<FormContainerEdit
|
||||||
schemaName={schema}
|
schemaName={fkSchema}
|
||||||
resourceBasePath={basePath}
|
resourceBasePath={basePath}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
uiSchema={{}}
|
uiSchema={{}}
|
||||||
@@ -233,7 +251,8 @@ const FormContainerNew = (props: FormContainerProps) => {
|
|||||||
const { schemaName, resourceBasePath, resource, uiSchema = {}, onSuccess } = props;
|
const { schemaName, resourceBasePath, resource, uiSchema = {}, onSuccess } = props;
|
||||||
const { onFinish } = useForm({
|
const { onFinish } = useForm({
|
||||||
resource: `${resourceBasePath}/${resource}`,
|
resource: `${resourceBasePath}/${resource}`,
|
||||||
action: "create"
|
action: "create",
|
||||||
|
onMutationSuccess: data => onSuccess(data.data)
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -242,25 +261,14 @@ const FormContainerNew = (props: FormContainerProps) => {
|
|||||||
schemaName={schemaName}
|
schemaName={schemaName}
|
||||||
uiSchema={uiSchema}
|
uiSchema={uiSchema}
|
||||||
resourceBasePath={resourceBasePath}
|
resourceBasePath={resourceBasePath}
|
||||||
onSubmit={(data:any) => {
|
onSubmit={(data:any) => { onFinish(data);}}
|
||||||
onFinish(data);
|
/>
|
||||||
onSuccess(data);
|
|
||||||
}} />
|
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Preview = (props: {id: string, resource: string, basePath: string, displayedFields: [string]}) => {
|
const Preview = (props: {record: any, displayedFields: [string]}) => {
|
||||||
const { basePath, resource, id, displayedFields } = props
|
const { record, displayedFields } = props
|
||||||
|
|
||||||
const { data, isLoading } = useOne({
|
|
||||||
resource: `${basePath}/${resource}`,
|
|
||||||
id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoading || data === undefined) {
|
|
||||||
return <CircularProgress />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
@@ -268,10 +276,29 @@ const Preview = (props: {id: string, resource: string, basePath: string, display
|
|||||||
return (
|
return (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
<Grid2 size={2}><Container>{field}</Container></Grid2>
|
<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>
|
</Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Grid2>
|
</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 = {
|
type ResourceContextType = {
|
||||||
basePath: string,
|
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 == "card") {
|
||||||
|
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 }
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import TextField from "@mui/material/TextField";
|
|||||||
import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
|
import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
|
||||||
import Autocomplete from "@mui/material/Autocomplete";
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
import { FirmContext } from "../../../contexts/FirmContext";
|
import { FirmContext } from "../../../contexts/FirmContext";
|
||||||
import { useContext } from "react";
|
import { Fragment, useContext } from "react";
|
||||||
import { Box, Grid2, styled } from "@mui/material";
|
import { Box, Grid2, styled } from "@mui/material";
|
||||||
import Stack from "@mui/material/Stack";
|
import Stack from "@mui/material/Stack";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -25,9 +25,9 @@ const FilterForm = (props: FilterFormProps) => {
|
|||||||
let currentValue = values
|
let currentValue = values
|
||||||
|
|
||||||
const formField = fields.filter(f => f.name != "search").map((f, index) =>
|
const formField = fields.filter(f => f.name != "search").map((f, index) =>
|
||||||
<>
|
<Fragment key={`${f.name}-${index}`} >
|
||||||
{ f.name == "created_at" && <Box width="100%" key={`created_at_break-${index}`} /> }
|
{ f.name == "created_at" && <Box width="100%" /> }
|
||||||
<Grid2 size={6} key={`${f.name}-${index}`} >
|
<Grid2 size={6}>
|
||||||
<FilterFormField
|
<FilterFormField
|
||||||
field={f}
|
field={f}
|
||||||
value={values.hasOwnProperty(f.name) ? values[f.name] : {}}
|
value={values.hasOwnProperty(f.name) ? values[f.name] : {}}
|
||||||
@@ -53,7 +53,7 @@ const FilterForm = (props: FilterFormProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Navigate, Route, Routes, useParams } from "react-router";
|
import dayjs from "dayjs";
|
||||||
import { CircularProgress } from "@mui/material";
|
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
|
import { Navigate, Route, Routes, useParams } from "react-router";
|
||||||
|
import { Box, Button, CircularProgress, Container, DialogContent, Modal, Paper } from "@mui/material";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import PreviewIcon from '@mui/icons-material/Preview';
|
||||||
import { useOne, useTranslation } from "@refinedev/core";
|
import { useOne, useTranslation } from "@refinedev/core";
|
||||||
import { BaseForm } from "../../lib/crud/components/base-form";
|
import { BaseForm } from "../../lib/crud/components/base-form";
|
||||||
import { ForeignKeyReference, ForeignKeySchema } from "../../lib/crud/components/widgets/foreign-key";
|
import { ForeignKeyReference, ForeignKeySchema } from "../../lib/crud/components/widgets/foreign-key";
|
||||||
@@ -10,7 +13,6 @@ import List from "./base-page/List";
|
|||||||
import Edit from "./base-page/Edit";
|
import Edit from "./base-page/Edit";
|
||||||
import New from "./base-page/New";
|
import New from "./base-page/New";
|
||||||
import { Contract } from "./ContractRoutes";
|
import { Contract } from "./ContractRoutes";
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
type Draft = {
|
type Draft = {
|
||||||
id: string,
|
id: string,
|
||||||
@@ -50,7 +52,7 @@ const EditDraft = () => {
|
|||||||
return <CircularProgress />
|
return <CircularProgress />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data?.data) {
|
if (record_id == undefined || !data?.data) {
|
||||||
return <Navigate to="../" />
|
return <Navigate to="../" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,12 +65,56 @@ const EditDraft = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<DraftPreview resourceBasePath={resourceBasePath} recordId={record_id}/>
|
||||||
<Edit<Draft> resource={"contracts/drafts"} schemaName={"ContractDraft"} uiSchema={uiSchema} />
|
<Edit<Draft> resource={"contracts/drafts"} schemaName={"ContractDraft"} uiSchema={uiSchema} />
|
||||||
<ContractCreate draft={draft}></ContractCreate>
|
<ContractCreate draft={draft}></ContractCreate>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DraftPreview = (props: {resourceBasePath: string, recordId: string}) => {
|
||||||
|
const { resourceBasePath, recordId } = props
|
||||||
|
const [openPreviewModal, setOpenPreviewModal] = React.useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button variant="outlined" onClick={() => setOpenPreviewModal(true)} color="primary" >
|
||||||
|
<PreviewIcon />Preview
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
open={openPreviewModal}
|
||||||
|
onClose={() => setOpenPreviewModal(false)}
|
||||||
|
aria-labelledby="modal-modal-title"
|
||||||
|
aria-describedby="modal-modal-description"
|
||||||
|
>
|
||||||
|
<DialogContent>
|
||||||
|
<Container>
|
||||||
|
<Paper>
|
||||||
|
<Stack
|
||||||
|
direction={"row"}
|
||||||
|
spacing={2}
|
||||||
|
sx={{
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box padding={"45px"}>
|
||||||
|
<iframe
|
||||||
|
src={`/api/v1/${resourceBasePath}/contracts/preview/draft/${recordId}`}
|
||||||
|
width="675px"
|
||||||
|
height="955px"
|
||||||
|
style={{ backgroundColor: "white", border: "1px solid black" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Container>
|
||||||
|
</DialogContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const ContractCreate = (props: { draft: any}) => {
|
const ContractCreate = (props: { draft: any}) => {
|
||||||
const { translate: t } = useTranslation();
|
const { translate: t } = useTranslation();
|
||||||
const { draft } = props;
|
const { draft } = props;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const ProvisionRoutes = () => {
|
|||||||
const ListProvision = () => {
|
const ListProvision = () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{ field: "label", column: { flex: 1 }},
|
{ field: "label", column: { flex: 1 }},
|
||||||
|
{ field: "updated_at", column: { width: 160 }},
|
||||||
];
|
];
|
||||||
return <List<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplate"} columns={columns} />
|
return <List<Provision> resource={`templates/provisions`} schemaName={"ProvisionTemplate"} columns={columns} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const TemplateRoutes = () => {
|
|||||||
const ListTemplate = () => {
|
const ListTemplate = () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{ field: "label", column: { flex: 1 }},
|
{ field: "label", column: { flex: 1 }},
|
||||||
|
{ field: "updated_at", column: { width: 160 }},
|
||||||
];
|
];
|
||||||
return <List<Template> resource={`templates/contracts`} schemaName={"ContractTemplate"} columns={columns} />
|
return <List<Template> resource={`templates/contracts`} schemaName={"ContractTemplate"} columns={columns} />
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user