29 Commits

Author SHA1 Message Date
3dc91b329f Adding the "numbered" props to array field to display value position in array 2025-05-03 20:33:25 +02:00
2f2c5a035d Adding id as a metadata file come card ressource 2025-05-03 20:32:20 +02:00
32ce981d40 Customizing ArrayField template to control numbers of items per row (in a json schema stardardish way) 2025-05-03 19:56:27 +02:00
e7a4389fde Displaying Card for contract 2025-05-03 18:31:19 +02:00
f03f8374c8 Allowing crud forms with card format (showing read only fields) 2025-05-03 18:27:16 +02:00
78ffcb9b71 Allowing RichtextWidget to be readonly 2025-05-03 18:24:27 +02:00
0a657dca4b Removing meta fields from card ressource 2025-05-03 18:22:47 +02:00
8941d69ba4 Displacing non-standard string format to props 2025-05-03 18:21:37 +02:00
b8d9e8e804 Removing display related information in models 2025-05-03 18:20:31 +02:00
4bf414112a Adding canceled status to Contract -Not implemented yet- 2025-05-03 18:04:26 +02:00
6cc99812d2 Liste Translations 2025-05-02 17:39:38 +02:00
2b88e46ca6 Changing theme works for compatibility with mui localization 2025-05-02 16:40:42 +02:00
2c23992e52 Improve translation of dashboard and list 2025-05-02 11:59:10 +02:00
ba46c10449 Improving Cartouche 2025-05-02 11:45:45 +02:00
f878fa7886 Light reformating 2025-05-02 01:49:14 +02:00
ef43369425 Movinf card title from cartouche to edit 2025-05-02 00:56:51 +02:00
37193d2246 Implementing the partner map for author identifying 2025-05-02 00:51:05 +02:00
87f9119b0b Upgrading TipTap indent extension 2025-05-02 00:09:56 +02:00
f9b6aae927 Implementing the partner into the db and contracts 2025-05-02 00:03:08 +02:00
6683d60be5 Adding a read only resource schema 2025-05-01 22:43:40 +02:00
3942c54ad9 Translating Edit and New base page 2025-05-01 22:43:17 +02:00
0a22bc1b8f Implementing Contract creation on draft page 2025-05-01 22:42:40 +02:00
237f8d5742 Updating changePropertiesOrder method 2025-05-01 19:58:48 +02:00
8d72172e0a Adding a Cartouche for the card component 2025-05-01 19:44:19 +02:00
90aa5e06f2 Adding default value for corporation activity on Firm initialization 2025-05-01 19:43:22 +02:00
178f27cfe2 Moving Refine Form logic from crud lib to implementation 2025-05-01 19:42:39 +02:00
f4c6cdab3b More explicit schema provider error when resource is not found 2025-05-01 19:34:40 +02:00
40e20d0e64 Creating Card Resources that readonly non updatable resources fields 2025-04-30 00:12:52 +02:00
3a5a299b53 Upgrading gui packages 2025-04-29 23:58:26 +02:00
35 changed files with 716 additions and 287 deletions

View File

@@ -17,6 +17,7 @@ class ContractStatus(str, Enum):
signed = 'signed' signed = 'signed'
printed = 'printed' printed = 'printed'
executed = 'executed' executed = 'executed'
canceled = 'canceled'
class ContractDraftStatus(str, Enum): class ContractDraftStatus(str, Enum):
@@ -94,15 +95,8 @@ class ContractDraft(CrudDocument):
name: str = Field(title="Nom") name: str = Field(title="Nom")
title: str = Field(title="Titre") title: str = Field(title="Titre")
parties: List[DraftParty] = Field(title="Parties") parties: List[DraftParty] = Field(title="Parties")
provisions: List[DraftProvision] = Field( provisions: List[DraftProvision] = Field(title='Clauses')
props={"items-per-row": "1", "numbered": True}, variables: List[DictionaryEntry] = Field(default=[], title='Variables')
title='Clauses'
)
variables: List[DictionaryEntry] = Field(
default=[],
format="dictionary",
title='Variables'
)
status: ContractDraftStatus = Field(default=ContractDraftStatus.in_progress, title="Statut") status: ContractDraftStatus = Field(default=ContractDraftStatus.in_progress, title="Statut")
todo: List[str] = Field(default=[], title="Reste à faire") todo: List[str] = Field(default=[], title="Reste à faire")
@@ -165,7 +159,7 @@ class Contract(CrudDocument):
title: str = Field(title="Titre") title: str = Field(title="Titre")
parties: List[Party] = Field(title="Parties") parties: List[Party] = Field(title="Parties")
provisions: List[Provision] = Field( provisions: List[Provision] = Field(
props={"items-per-row": "1", "numbered": True}, props={"items_per_row": "1", "numbered": True},
title='Clauses' title='Clauses'
) )
status: ContractStatus = Field(default=ContractStatus.published, title="Statut") status: ContractStatus = Field(default=ContractStatus.published, title="Statut")

View File

@@ -31,8 +31,7 @@ async def create(schema: ContractCreate, reg=Depends(get_authed_tenant_registry)
contract_dict = schema.model_dump() contract_dict = schema.model_dump()
del(contract_dict['draft_id']) del(contract_dict['draft_id'])
lawyer = await Entity.get(reg.db, reg.user.entity_id) contract_dict['lawyer'] = reg.partner.model_dump()
contract_dict['lawyer'] = lawyer.model_dump()
contract_dict['name'] = draft.name contract_dict['name'] = draft.name
contract_dict['title'] = draft.title contract_dict['title'] = draft.title

View File

@@ -1,13 +1,14 @@
import datetime import datetime
from typing import List from typing import List
from beanie import PydanticObjectId
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from firm.contract.models import ContractDraft, DraftProvision, DraftParty, Contract from firm.contract.models import ContractDraft, DraftProvision, DraftParty, Contract
from firm.entity.models import Entity from firm.entity.models import Entity
from firm.core.schemas import Writer, Reader from firm.core.schemas import Writer, Reader
from firm.core.models import DictionaryEntry from firm.core.models import DictionaryEntry, ForeignKey
class ContractDraftRead(Reader, ContractDraft): class ContractDraftRead(Reader, ContractDraft):
@@ -19,12 +20,12 @@ class ContractDraftCreate(Writer):
title: str = Field(title='Titre') title: str = Field(title='Titre')
parties: List[DraftParty] = Field(title='Parties') parties: List[DraftParty] = Field(title='Parties')
provisions: List[DraftProvision] = Field( provisions: List[DraftProvision] = Field(
props={"items-per-row": "1", "numbered": True}, props={"items_per_row": "1", "numbered": True},
title='Clauses' title='Clauses'
) )
variables: List[DictionaryEntry] = Field( variables: List[DictionaryEntry] = Field(
default=[], default=[],
format="dictionary", props={"display": "dictionary"},
title='Variables' title='Variables'
) )
@@ -49,7 +50,7 @@ class ForeignEntityRead(BaseModel):
class PartyRead(BaseModel): class PartyRead(BaseModel):
signature_affixed: bool = Field(title='Signature apposée?') signature_affixed: bool = Field(title='Signature apposée?')
signature_uuid: str = Field(format="signature-link", title="Lien vers signature") signature_uuid: str = Field(props={"display": "signature-link"}, title="Lien vers signature")
part: str = Field(title='Rôle') part: str = Field(title='Rôle')
entity: ForeignEntityRead = Field(title='Client') entity: ForeignEntityRead = Field(title='Client')
@@ -58,7 +59,10 @@ class PartyRead(BaseModel):
class ContractRead(Reader, Contract): class ContractRead(Reader, Contract):
parties: List[PartyRead] parties: List[PartyRead] = Field(
props={"items_per_row": "2"},
title='Parties'
)
lawyer: ForeignEntityRead lawyer: ForeignEntityRead
class Config: class Config:
@@ -68,7 +72,7 @@ class ContractRead(Reader, Contract):
class ContractCreate(Writer): class ContractCreate(Writer):
date: datetime.date date: datetime.date
location: str location: str
draft_id: str draft_id: PydanticObjectId = ForeignKey(resource="contracts/drafts", schema="ContractDraft")
class ContractInit(BaseModel): class ContractInit(BaseModel):
date: datetime.date date: datetime.date

View File

@@ -1,11 +1,15 @@
from fastapi import HTTPException, Depends from fastapi import HTTPException, Depends
from hub.auth import get_current_user from hub.auth import get_current_user
from firm.current_firm import CurrentFirmModel, Partner
from firm.db import get_db_client from firm.db import get_db_client
from firm.current_firm import CurrentFirmModel from firm.entity.models import Entity
class Registry: class Registry:
user = None user = None
partner = None
def __init__(self, db_client, instance, firm): def __init__(self, db_client, instance, firm):
self.db = db_client[f"tenant_{instance}_{firm}"] self.db = db_client[f"tenant_{instance}_{firm}"]
@@ -14,11 +18,14 @@ class Registry:
self.current_firm = CurrentFirmModel.get_current(self.db) self.current_firm = CurrentFirmModel.get_current(self.db)
def set_user(self, user): async def set_user(self, user):
for firm in user.firms: for firm in user.firms:
if firm.instance == self.instance and firm.firm == self.firm: if firm.instance == self.instance and firm.firm == self.firm:
partner = await Partner.get_by_user_id(self.db, user.id)
partner_entity = await Entity.get(self.db, partner.entity_id)
self.user = user self.user = user
self.db.user = user self.partner = partner_entity
self.db.partner = partner_entity
return return
raise PermissionError raise PermissionError
@@ -30,9 +37,9 @@ async def get_tenant_registry(instance: str, firm: str, db_client=Depends(get_db
return registry return registry
def get_authed_tenant_registry(registry=Depends(get_tenant_registry), user=Depends(get_current_user)) -> Registry: async def get_authed_tenant_registry(registry=Depends(get_tenant_registry), user=Depends(get_current_user)) -> Registry:
try: try:
registry.set_user(user) await registry.set_user(user)
except PermissionError: except PermissionError:
raise HTTPException(status_code=404, detail="This firm doesn't exist or you are not allowed to access it.") raise HTTPException(status_code=404, detail="This firm doesn't exist or you are not allowed to access it.")
@@ -44,7 +51,7 @@ async def get_uninitialized_registry(instance: str, firm: str, db_client=Depends
raise HTTPException(status_code=409, detail="Firm configuration already exists") raise HTTPException(status_code=409, detail="Firm configuration already exists")
try: try:
registry.set_user(user) await registry.set_user(user)
except PermissionError: except PermissionError:
raise HTTPException(status_code=404, detail="This firm doesn't exist or you are not allowed to access it.") raise HTTPException(status_code=404, detail="This firm doesn't exist or you are not allowed to access it.")

View File

@@ -37,7 +37,7 @@ class CrudDocument(BaseModel):
@classmethod @classmethod
async def create(cls, db, create_schema): async def create(cls, db, create_schema):
model_dict = create_schema.model_dump() | {"created_by": db.user.id, "updated_by": db.user.id} model_dict = create_schema.model_dump() | {"created_by": db.partner.id, "updated_by": db.partner.id}
document = cls.model_validate(model_dict).model_dump(mode="json") document = cls.model_validate(model_dict).model_dump(mode="json")
result = await cls._get_collection(db).insert_one(document) result = await cls._get_collection(db).insert_one(document)
@@ -52,8 +52,13 @@ class CrudDocument(BaseModel):
} }
@classmethod @classmethod
def list(cls, db): async def list(cls, db, criteria={}):
return cls._get_collection(db).find({}) result = []
for document in await cls._get_collection(db).find(criteria).to_list():
document["id"] = document.pop("_id")
result.append(cls.model_validate(document))
return result
@classmethod @classmethod
async def get(cls, db, model_id): async def get(cls, db, model_id):
@@ -66,7 +71,7 @@ class CrudDocument(BaseModel):
@classmethod @classmethod
async def update(cls, db, model, update_schema): async def update(cls, db, model, update_schema):
model_dict = update_schema.model_dump(mode="json") | {"updated_by": db.user.id} model_dict = update_schema.model_dump(mode="json") | {"updated_by": db.partner.id}
update_query = { update_query = {
"$set": {field: value for field, value in model_dict.items() if field!= "id" } "$set": {field: value for field, value in model_dict.items() if field!= "id" }
} }

View File

@@ -36,14 +36,15 @@ class CurrentFirmModel(CrudDocument):
class CurrentFirmSchemaRead(Reader): class CurrentFirmSchemaRead(Reader):
entity: EntityRead entity: EntityRead
partner: EntityRead partner: EntityRead
partner_list: list[EntityRead]
instance: str instance: str
firm: str firm: str
primary_color: str primary_color: str
secondary_color: str secondary_color: str
@classmethod @classmethod
def from_model_and_entities(cls, model, entity, partner): def from_model_and_entities(cls, model, entity, partner, partner_list):
schema = cls(**model.model_dump(mode="json"), entity=entity, partner=partner) schema = cls(**model.model_dump(mode="json"), entity=entity, partner=partner, partner_list=partner_list)
return schema return schema
class CurrentFirmSchemaCreate(Writer): class CurrentFirmSchemaCreate(Writer):

View File

@@ -10,10 +10,17 @@ current_firm_router = APIRouter()
@current_firm_router.get("/", response_model=CurrentFirmSchemaRead, response_description=f"Current Firm records retrieved") @current_firm_router.get("/", response_model=CurrentFirmSchemaRead, response_description=f"Current Firm records retrieved")
async def read(reg=Depends(get_authed_tenant_registry)) -> CurrentFirmSchemaRead: async def read(reg=Depends(get_authed_tenant_registry)) -> CurrentFirmSchemaRead:
document = await CurrentFirmModel.get_current(reg.db) document = await CurrentFirmModel.get_current(reg.db)
entity = await Entity.get(reg.db, document.entity_id) firm_entity = await Entity.get(reg.db, document.entity_id)
partner = await Partner.get_by_user_id(reg.db, reg.user.id) partner = await Partner.get_by_user_id(reg.db, reg.user.id)
partner = await Entity.get(reg.db, partner.entity_id) partner = await Entity.get(reg.db, partner.entity_id)
return CurrentFirmSchemaRead.from_model_and_entities(document, EntityRead.from_model(entity), EntityRead.from_model(partner)) partner_list = await Partner.list(reg.db)
partner_list = await Entity.list(reg.db, {"_id": {"$in": [p.entity_id for p in partner_list]}})
return CurrentFirmSchemaRead.from_model_and_entities(
document,
EntityRead.from_model(firm_entity),
EntityRead.from_model(partner),
[EntityRead.from_model(p) for p in partner_list]
)
@current_firm_router.post("/", response_description=f"Current Firm added to the database") @current_firm_router.post("/", response_description=f"Current Firm added to the database")
async def create(schema: CurrentFirmSchemaCreate, reg=Depends(get_uninitialized_registry)) -> CurrentFirmSchemaRead: async def create(schema: CurrentFirmSchemaCreate, reg=Depends(get_uninitialized_registry)) -> CurrentFirmSchemaRead:

View File

@@ -21,7 +21,7 @@ class Individual(EntityType):
lastname: Indexed(str) = Field(title='Nom de famille') lastname: Indexed(str) = Field(title='Nom de famille')
surnames: List[Indexed(str)] = Field( surnames: List[Indexed(str)] = Field(
default=[], default=[],
props={"items-per-row": "4", "numbered": True}, props={"items_per_row": "4", "numbered": True},
title="Surnoms" title="Surnoms"
) )
day_of_birth: Optional[date] = Field(default=None, title='Date de naissance') day_of_birth: Optional[date] = Field(default=None, title='Date de naissance')

View File

@@ -85,16 +85,8 @@ class ContractTemplate(CrudDocument):
name: str = Field(title="Nom") name: str = Field(title="Nom")
title: str = Field(title="Titre") title: str = Field(title="Titre")
parties: List[PartyTemplate] = Field(default=[], title="Parties") parties: List[PartyTemplate] = Field(default=[], title="Parties")
provisions: List[ProvisionTemplateReference] = Field( provisions: List[ProvisionTemplateReference] = Field(default=[], title="Clauses")
default=[], variables: List[DictionaryEntry] = Field(default=[], title="Variables")
props={"items-per-row": "1", "numbered": True},
title="Clauses"
)
variables: List[DictionaryEntry] = Field(
default=[],
format="dictionary",
title="Variables"
)
def compute_label(self) -> str: def compute_label(self) -> str:
return f"{self.name} - \"{unescape(remove_html_tags(self.title))}\"" return f"{self.name} - \"{unescape(remove_html_tags(self.title))}\""

View File

@@ -16,13 +16,12 @@ class ContractTemplateCreate(Writer):
parties: List[PartyTemplate] = Field(default=[], title="Parties") parties: List[PartyTemplate] = Field(default=[], title="Parties")
provisions: List[ProvisionTemplateReference] = Field( provisions: List[ProvisionTemplateReference] = Field(
default=[], default=[],
props={"items-per-row": "1", "numbered": True}, props={"items_per_row": "1", "numbered": True},
title="Clauses" title="Clauses"
) )
variables: List[DictionaryEntry] = Field( variables: List[DictionaryEntry] = Field(
default=[], default=[],
format="dictionary", props={"display": "dictionary", "required": False},
props={"required": False},
title="Variables" title="Variables"
) )

View File

@@ -35,6 +35,7 @@
"@tiptap/extension-underline": "^2.11.7", "@tiptap/extension-underline": "^2.11.7",
"@tiptap/react": "^2.11.7", "@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7", "@tiptap/starter-kit": "^2.11.7",
"dayjs": "^1.11.13",
"i18next": "^25.0.1", "i18next": "^25.0.1",
"i18next-browser-languagedetector": "^8.0.5", "i18next-browser-languagedetector": "^8.0.5",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
@@ -2622,9 +2623,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz",
"integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2636,9 +2637,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz",
"integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2650,9 +2651,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz",
"integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2664,9 +2665,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz",
"integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2678,9 +2679,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-arm64": { "node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz",
"integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2692,9 +2693,9 @@
] ]
}, },
"node_modules/@rollup/rollup-freebsd-x64": { "node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz",
"integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2706,9 +2707,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz",
"integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2720,9 +2721,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz",
"integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -2734,9 +2735,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz",
"integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2748,9 +2749,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz",
"integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2762,9 +2763,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-loongarch64-gnu": { "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz",
"integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@@ -2776,9 +2777,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz",
"integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@@ -2790,9 +2791,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz",
"integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2804,9 +2805,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-musl": { "node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz",
"integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@@ -2818,9 +2819,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz",
"integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -2832,9 +2833,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz",
"integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2846,9 +2847,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz",
"integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -2860,9 +2861,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz",
"integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -2874,9 +2875,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz",
"integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -2888,9 +2889,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz",
"integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -5035,9 +5036,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.142", "version": "1.5.144",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.142.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.144.tgz",
"integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w==", "integrity": "sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
@@ -5978,9 +5979,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/flow-parser": { "node_modules/flow-parser": {
"version": "0.268.0", "version": "0.269.1",
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.268.0.tgz", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.269.1.tgz",
"integrity": "sha512-URZmPy/jKDDIJUHUfC+5KNwaPcfONTL3R8xltQWVEoCKLWowVebEBg89nbAnYHNo6ev8KzKWFpOROfHZdaCoxA==", "integrity": "sha512-2Yr0kqvT7RwaGL192nT78O5AWJeECQjl0NEzBkMsx8OJt63BvNl5yvSIbE4qZ1VDSjEkhbUgaWYdwX354bVNjw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
@@ -6525,9 +6526,9 @@
} }
}, },
"node_modules/i18next": { "node_modules/i18next": {
"version": "25.0.1", "version": "25.0.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.1.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.2.tgz",
"integrity": "sha512-8S8PyZbrymJZn3DaN70/34JYWNhsqrU6yA4MuzcygJBv+41dgNMocEA8h+kV1P7MCc1ll03lOTOIXE7mpNCicw==", "integrity": "sha512-xWxgK8GAaPYkV9ia2tdgbtdM+qiC+ysVTBPvXhpCORU/+QkeQe3BSI7Crr+c4ZXULN1PfnXG/HY2n7HGx4KKBg==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -9289,9 +9290,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.5.2", "version": "7.5.3",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
"integrity": "sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==", "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^1.0.1", "cookie": "^1.0.1",
@@ -9576,9 +9577,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.40.0", "version": "4.40.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -9592,26 +9593,26 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.40.0", "@rollup/rollup-android-arm-eabi": "4.40.1",
"@rollup/rollup-android-arm64": "4.40.0", "@rollup/rollup-android-arm64": "4.40.1",
"@rollup/rollup-darwin-arm64": "4.40.0", "@rollup/rollup-darwin-arm64": "4.40.1",
"@rollup/rollup-darwin-x64": "4.40.0", "@rollup/rollup-darwin-x64": "4.40.1",
"@rollup/rollup-freebsd-arm64": "4.40.0", "@rollup/rollup-freebsd-arm64": "4.40.1",
"@rollup/rollup-freebsd-x64": "4.40.0", "@rollup/rollup-freebsd-x64": "4.40.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.40.0", "@rollup/rollup-linux-arm-gnueabihf": "4.40.1",
"@rollup/rollup-linux-arm-musleabihf": "4.40.0", "@rollup/rollup-linux-arm-musleabihf": "4.40.1",
"@rollup/rollup-linux-arm64-gnu": "4.40.0", "@rollup/rollup-linux-arm64-gnu": "4.40.1",
"@rollup/rollup-linux-arm64-musl": "4.40.0", "@rollup/rollup-linux-arm64-musl": "4.40.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.40.0", "@rollup/rollup-linux-loongarch64-gnu": "4.40.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1",
"@rollup/rollup-linux-riscv64-gnu": "4.40.0", "@rollup/rollup-linux-riscv64-gnu": "4.40.1",
"@rollup/rollup-linux-riscv64-musl": "4.40.0", "@rollup/rollup-linux-riscv64-musl": "4.40.1",
"@rollup/rollup-linux-s390x-gnu": "4.40.0", "@rollup/rollup-linux-s390x-gnu": "4.40.1",
"@rollup/rollup-linux-x64-gnu": "4.40.0", "@rollup/rollup-linux-x64-gnu": "4.40.1",
"@rollup/rollup-linux-x64-musl": "4.40.0", "@rollup/rollup-linux-x64-musl": "4.40.1",
"@rollup/rollup-win32-arm64-msvc": "4.40.0", "@rollup/rollup-win32-arm64-msvc": "4.40.1",
"@rollup/rollup-win32-ia32-msvc": "4.40.0", "@rollup/rollup-win32-ia32-msvc": "4.40.1",
"@rollup/rollup-win32-x64-msvc": "4.40.0", "@rollup/rollup-win32-x64-msvc": "4.40.1",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },

View File

@@ -31,6 +31,7 @@
"@tiptap/extension-underline": "^2.11.7", "@tiptap/extension-underline": "^2.11.7",
"@tiptap/react": "^2.11.7", "@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.11.7", "@tiptap/starter-kit": "^2.11.7",
"dayjs": "^1.11.13",
"i18next": "^25.0.1", "i18next": "^25.0.1",
"i18next-browser-languagedetector": "^8.0.5", "i18next-browser-languagedetector": "^8.0.5",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",

View File

@@ -171,6 +171,12 @@
} }
}, },
"schemas": { "schemas": {
"created_by": "Created by",
"created_at": "Created at",
"updated_by": "Updated by",
"updated_at": "Updated at",
"label": "Label",
"type": "Type",
"individual": { "individual": {
"type": "Individual", "type": "Individual",
"lastname": "Lastname", "lastname": "Lastname",
@@ -203,13 +209,15 @@
"entity": { "entity": {
"entity_data": "Informations", "entity_data": "Informations",
"address": "Address", "address": "Address",
"resource_title": "Entity" "resource_title": "Entity",
"resource_plural": "Entities"
}, },
"provision_template": { "provision_template": {
"name": "Name", "name": "Name",
"title": "Title", "title": "Title",
"body": "Body", "body": "Body",
"resource_title": "Provision Template" "resource_title": "Provision Template",
"resource_plural": "Provision Templates"
}, },
"contract_template": { "contract_template": {
"name": "Name", "name": "Name",
@@ -217,7 +225,8 @@
"provisions": "Provisions", "provisions": "Provisions",
"parties": "Parties", "parties": "Parties",
"variables": "Variables", "variables": "Variables",
"resource_title": "Contract Template" "resource_title": "Contract Template",
"resource_plural": "Contract Templates"
}, },
"party_template": { "party_template": {
"entity_id": "Party Template", "entity_id": "Party Template",
@@ -240,7 +249,8 @@
"parties": "Parties", "parties": "Parties",
"provisions": "Provisions", "provisions": "Provisions",
"variables": "Variables", "variables": "Variables",
"resource_title": "Contract Draft" "resource_title": "Contract Draft",
"resource_plural": "Contract Drafts"
}, },
"draft_party": { "draft_party": {
"entity_id": "Client", "entity_id": "Client",
@@ -267,6 +277,7 @@
"date": "Date", "date": "Date",
"location": "Location", "location": "Location",
"resource_title": "Contract", "resource_title": "Contract",
"resource_plural": "Contracts",
"draft_id": "Draft" "draft_id": "Draft"
} }
} }

View File

@@ -171,6 +171,12 @@
} }
}, },
"schemas": { "schemas": {
"created_by": "Créé par",
"created_at": "Créé le",
"updated_by": "Modifié par",
"updated_at": "Modifié le",
"label": "Label",
"type": "Type",
"individual": { "individual": {
"type": "Particulier", "type": "Particulier",
"middlename": "Autres prénoms", "middlename": "Autres prénoms",
@@ -203,13 +209,15 @@
"entity": { "entity": {
"entity_data": "Informations", "entity_data": "Informations",
"address": "Adresse", "address": "Adresse",
"resource_title": "Entité" "resource_title": "Entité",
"resource_plural": "Entités"
}, },
"provision_template": { "provision_template": {
"name": "Nom", "name": "Nom",
"body": "Corps", "body": "Corps",
"title": "Titre", "title": "Titre",
"resource_title": "Template de Clause" "resource_title": "Template de Clause",
"resource_plural": "Templates de Clauses"
}, },
"contract_template": { "contract_template": {
"name": "Nom", "name": "Nom",
@@ -217,7 +225,8 @@
"parties": "Parties", "parties": "Parties",
"provisions": "Clauses", "provisions": "Clauses",
"variables": "Variables", "variables": "Variables",
"resource_title": "Template de Contrat" "resource_title": "Template de Contrat",
"resource_plural": "Templates de Contrats"
}, },
"party_template": { "party_template": {
"entity_id": "Entité", "entity_id": "Entité",
@@ -240,7 +249,8 @@
"title": "Titre", "title": "Titre",
"provisions": "Clauses", "provisions": "Clauses",
"variables": "Variables", "variables": "Variables",
"resource_title": "Brouillon de Contrat" "resource_title": "Brouillon de Contrat",
"resource_plural": "Brouillons de Contrats"
}, },
"draft_party": { "draft_party": {
"part": "Rôle", "part": "Rôle",
@@ -266,6 +276,7 @@
"contract": { "contract": {
"draft_id": "Brouillon", "draft_id": "Brouillon",
"resource_title": "Contrat", "resource_title": "Contrat",
"resource_plural": "Contrats",
"location": "Lieu", "location": "Lieu",
"date": "Date" "date": "Date"
} }

View File

@@ -5,7 +5,6 @@ import { RefineSnackbarProvider, useNotificationProvider } from "@refinedev/mui"
import CssBaseline from "@mui/material/CssBaseline"; import CssBaseline from "@mui/material/CssBaseline";
import GlobalStyles from "@mui/material/GlobalStyles"; import GlobalStyles from "@mui/material/GlobalStyles";
import { ThemeProvider } from "@mui/material/styles";
import HistoryEduIcon from '@mui/icons-material/HistoryEdu'; import HistoryEduIcon from '@mui/icons-material/HistoryEdu';
import routerBindings, { import routerBindings, {
CatchAllNavigate, CatchAllNavigate,
@@ -22,9 +21,9 @@ import { ForgotPassword } from "./components/auth/ForgotPassword";
import { UpdatePassword } from "./components/auth/UpdatePassword"; import { UpdatePassword } from "./components/auth/UpdatePassword";
import { Header } from "./components"; import { Header } from "./components";
import { I18nTheme } from "./components/I18nTheme";
import { HubRoutes } from "./pages/hub"; import { HubRoutes } from "./pages/hub";
import { FirmRoutes } from "./pages/firm"; import { FirmRoutes } from "./pages/firm";
import rpcTheme from "./theme";
function App() { function App() {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
@@ -37,43 +36,43 @@ function App() {
return ( return (
<BrowserRouter> <BrowserRouter>
<ThemeProvider theme={rpcTheme}> <ColorModeContextProvider>
<ColorModeContextProvider> <CssBaseline />
<CssBaseline /> <GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} />
<GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} /> <RefineSnackbarProvider>
<RefineSnackbarProvider> <Refine
<Refine authProvider={authProvider}
authProvider={authProvider} dataProvider={dataProvider}
dataProvider={dataProvider} i18nProvider={i18nProvider}
i18nProvider={i18nProvider} notificationProvider={useNotificationProvider}
notificationProvider={useNotificationProvider} routerProvider={routerBindings}
routerProvider={routerBindings} options={{
options={{ title: {
title: { text: "Roleplay Contracts",
text: "Roleplay Contracts", icon: <HistoryEduIcon />
icon: <HistoryEduIcon /> },
}, syncWithLocation: true,
syncWithLocation: true, warnWhenUnsavedChanges: true,
warnWhenUnsavedChanges: true, useNewQueryKeys: true,
useNewQueryKeys: true, disableTelemetry: true,
disableTelemetry: true, reactQuery: {
reactQuery: { clientConfig: {
clientConfig: { defaultOptions: {
defaultOptions: { queries: {
queries: { retry: (failureCount, error) => {
retry: (failureCount, error) => { // @ts-ignore
// @ts-ignore if (error.statusCode >= 400 && error.statusCode <= 499) {
if (error.statusCode >= 400 && error.statusCode <= 499) { return false
return false }
} return failureCount < 4
return failureCount < 4 },
},
}
} }
} }
} }
}} }
> }}
>
<I18nTheme>
<Routes> <Routes>
<Route <Route
element={( element={(
@@ -95,10 +94,10 @@ function App() {
</Routes> </Routes>
<UnsavedChangesNotifier /> <UnsavedChangesNotifier />
<DocumentTitleHandler /> <DocumentTitleHandler />
</Refine> </I18nTheme>
</RefineSnackbarProvider> </Refine>
</ColorModeContextProvider> </RefineSnackbarProvider>
</ThemeProvider> </ColorModeContextProvider>
</BrowserRouter> </BrowserRouter>
); );
} }

View File

@@ -0,0 +1,40 @@
import { useTranslation } from "@refinedev/core";
import { useContext } from "react";
import { FirmContext } from "../contexts/FirmContext";
import Grid2 from '@mui/material/Grid2';
type CartoucheProps = {
record: any
}
const Cartouche = (props: CartoucheProps) => {
const { record } = props;
const { translate: t } = useTranslation();
return (
<>
<Grid2 container spacing={0}>
<Grid2 size={2}>{t("schemas.created_by")}:</Grid2>
<Grid2 size={4}><AuthorField partnerId={record.created_by} /></Grid2>
<Grid2 size={2}>{t("schemas.created_at")}:</Grid2>
<Grid2 size={4}>{new Date(record.created_at).toLocaleString()}</Grid2>
<Grid2 size={2}>{t("schemas.updated_by")}:</Grid2>
<Grid2 size={4}><AuthorField partnerId={record.updated_by} /></Grid2>
<Grid2 size={2}>{t("schemas.updated_at")}:</Grid2>
<Grid2 size={4}>{new Date(record.updated_at).toLocaleString()}</Grid2>
</Grid2>
</>
)
}
export default Cartouche;
const AuthorField = (props: {partnerId: string})=> {
const { partnerId } = props;
const { partnerMap } = useContext(FirmContext);
const { translate: t } = useTranslation();
if (partnerMap && partnerMap.has(partnerId)) {
return <>{ partnerMap.get(partnerId) }</>
}
return <>{t("REDACTED")}</>
}

View File

@@ -0,0 +1,20 @@
import React, { PropsWithChildren } from "react";
import { useTranslation } from "@refinedev/core";
import { useTheme } from "@mui/material";
import * as locales from '@mui/material/locale';
import { createTheme, ThemeProvider } from "@mui/material/styles";
type SupportedLocales = keyof typeof locales;
export const I18nTheme: React.FC<PropsWithChildren> = ({ children }: PropsWithChildren) => {
const { getLocale } = useTranslation();
const theme = useTheme()
const themeWithLocale = createTheme(theme, locales[getLocale() as SupportedLocales])
return (
<ThemeProvider theme={themeWithLocale}>
{ children }
</ThemeProvider>
);
}

View File

@@ -1,9 +1,12 @@
import React, { createContext, PropsWithChildren } from 'react'; import React, { createContext, PropsWithChildren } from 'react';
import { IFirm } from "../interfaces"; import { IFirm } from "../interfaces";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { useOne } from "@refinedev/core";
import { CircularProgress } from "@mui/material";
type FirmContextType = { type FirmContextType = {
currentFirm: IFirm, currentFirm: IFirm,
partnerMap?: Map<string, string>
} }
export const FirmContext = createContext<FirmContextType>( export const FirmContext = createContext<FirmContextType>(
@@ -12,13 +15,27 @@ export const FirmContext = createContext<FirmContextType>(
export const FirmContextProvider: React.FC<PropsWithChildren> = ({ children }: PropsWithChildren) => { export const FirmContextProvider: React.FC<PropsWithChildren> = ({ children }: PropsWithChildren) => {
const { instance, firm } = useParams<IFirm>() const { instance, firm } = useParams<IFirm>();
const { data, isError, error, isLoading } = useOne({resource: 'firm', id: `${instance}/${firm}/`, errorNotification: false});
if (instance === undefined || firm === undefined) { if (instance === undefined || firm === undefined) {
return "Error" return "Error"
} }
if (isLoading) {
return <CircularProgress />
}
let value: FirmContextType = {
currentFirm: {instance, firm}
}
if (!isError || error?.statusCode != 405) {
value.currentFirm.entity = data?.data.entity;
value.partnerMap = new Map(data?.data.partner_list.map((item: any) => [item.id, item.label]));
}
return ( return (
<FirmContext.Provider value={{currentFirm: {instance, firm}}} > <FirmContext.Provider value={value} >
{ children } { children }
</FirmContext.Provider> </FirmContext.Provider>
); );

View File

@@ -8,14 +8,14 @@ i18n
.use(detector) .use(detector)
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
supportedLngs: ["EN", "FR"], supportedLngs: ["enUS", "frFR"],
backend: { backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json", // "http/locales/{{lng}}/{{ns}}.json" loadPath: "/locales/{{lng}}/{{ns}}.json", // "http/locales/{{lng}}/{{ns}}.json"
}, },
//saveMissing: true, //saveMissing: true,
ns: ["common"], ns: ["common"],
defaultNS: "common", defaultNS: "common",
fallbackLng: ["EN", "FR"], fallbackLng: ["enUS", "frFR"],
}); });
export default i18n; export default i18n;

View File

@@ -2,6 +2,7 @@
export type IFirm = { export type IFirm = {
instance: string, instance: string,
firm: string firm: string
entity?: any
} }
type User = { type User = {

View File

@@ -3,6 +3,7 @@ import Form from "@rjsf/mui";
import { RegistryFieldsType, RegistryWidgetsType, RJSFSchema, UiSchema } from "@rjsf/utils"; import { RegistryFieldsType, RegistryWidgetsType, RJSFSchema, UiSchema } from "@rjsf/utils";
import CrudTextWidget from "./widgets/crud-text-widget"; import CrudTextWidget from "./widgets/crud-text-widget";
import UnionEnumField from "./fields/union-enum"; import UnionEnumField from "./fields/union-enum";
import ArrayFieldTemplate from "./templates/ArrayFieldTemplate"
import { ResourceContext } from "../contexts/ResourceContext"; import { ResourceContext } from "../contexts/ResourceContext";
import { ReactNode } from "react"; import { ReactNode } from "react";
@@ -24,6 +25,10 @@ export const customFields: RegistryFieldsType = {
AnyOfField: UnionEnumField AnyOfField: UnionEnumField
} }
const customTemplates = {
ArrayFieldTemplate
}
export const BaseForm: React.FC<BaseFormProps> = (props) => { export const BaseForm: React.FC<BaseFormProps> = (props) => {
const { schema, uiSchema, resourceBasePath, formData, children, onSubmit, onChange } = props; const { schema, uiSchema, resourceBasePath, formData, children, onSubmit, onChange } = props;
@@ -38,6 +43,7 @@ export const BaseForm: React.FC<BaseFormProps> = (props) => {
omitExtraData={true} omitExtraData={true}
widgets={customWidgets} widgets={customWidgets}
fields={customFields} fields={customFields}
templates={customTemplates}
onChange={(e, id) => onChange != undefined && onChange(e.formData)} onChange={(e, id) => onChange != undefined && onChange(e.formData)}
children={children} children={children}
/> />

View File

@@ -8,32 +8,32 @@ import { BaseForm } from "./base-form";
type CrudFormProps = { type CrudFormProps = {
schemaName: string, schemaName: string,
uiSchema?: UiSchema, uiSchema?: UiSchema,
resourceBasePath?: string, record?: any,
resource: string, resourceBasePath: string,
id?: string, onSubmit?: (data: any) => void,
onSuccess?: (data: any) => void,
defaultValue?: any, defaultValue?: any,
children?: ReactNode children?: ReactNode
card?: boolean
} }
export const CrudForm: React.FC<CrudFormProps> = (props) => { export const CrudForm: React.FC<CrudFormProps> = (props) => {
const { schemaName, uiSchema, resourceBasePath="" ,resource, id, onSuccess, defaultValue, children } = props; const { schemaName, uiSchema, record, resourceBasePath, defaultValue, children, onSubmit=(data: any) => {}, card=false } = props;
const { onFinish, query, formLoading } = useForm({
resource: resourceBasePath == "" ? resource : `${resourceBasePath}/${resource}`,
action: id === undefined ? "create" : "edit",
redirect: "show",
id,
onMutationSuccess: (data: any) => { if (onSuccess) { onSuccess(data) } },
});
const [schema, setSchema] = useState({}); const [schema, setSchema] = useState({});
const [schemaLoading, setSchemaLoading] = useState(true); const [schemaLoading, setSchemaLoading] = useState(true);
useEffect(() => { useEffect(() => {
const fetchSchema = async () => { const fetchSchema = async () => {
try { try {
const schemaFullName = id === undefined ? `${schemaName}Create` : `${schemaName}Update`; let resourceSchema
const resourceSchema = await jsonschemaProvider.getResourceSchema(schemaFullName); if (record === undefined) {
resourceSchema = await jsonschemaProvider.getCreateResourceSchema(schemaName);
} else {
if (card) {
resourceSchema = await jsonschemaProvider.getCardResourceSchema(schemaName);
} else {
resourceSchema = await jsonschemaProvider.getUpdateResourceSchema(schemaName);
}
}
setSchema(resourceSchema); setSchema(resourceSchema);
setSchemaLoading(false); setSchemaLoading(false);
} catch (error) { } catch (error) {
@@ -44,19 +44,18 @@ export const CrudForm: React.FC<CrudFormProps> = (props) => {
fetchSchema(); fetchSchema();
}, []); }, []);
if(formLoading || schemaLoading) { if(schemaLoading) {
return <CircularProgress /> return <CircularProgress />
} }
const record = query?.data?.data || defaultValue;
return ( return (
<BaseForm <BaseForm
schema={schema} schema={schema}
uiSchema={uiSchema} uiSchema={uiSchema}
formData={record} formData={record || defaultValue}
resourceBasePath={resourceBasePath} resourceBasePath={resourceBasePath}
onSubmit={ onSubmit={
(data: any) => onFinish(data) (data: any) => onSubmit(data)
} }
children={children} children={children}
/> />

View File

@@ -0,0 +1,105 @@
import Box from '@mui/material/Box';
import Grid2 from '@mui/material/Grid2';
import Paper from '@mui/material/Paper';
import {
getTemplate,
getUiOptions,
ArrayFieldTemplateProps,
ArrayFieldTemplateItemType,
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.
*
* @param props - The `ArrayFieldTemplateItemType` props for the component
*/
export default function ArrayFieldTemplate<
T = any,
S extends CrudTextRJSFSchema = CrudTextRJSFSchema,
F extends FormContextType = any
>(props: ArrayFieldTemplateProps<T, S, F>) {
const { canAdd, disabled, idSchema, uiSchema, items, onAddClick, readonly, registry, required, schema, title } =
props;
const uiOptions = getUiOptions<T, S, F>(uiSchema);
const ArrayFieldDescriptionTemplate = getTemplate<'ArrayFieldDescriptionTemplate', T, S, F>(
'ArrayFieldDescriptionTemplate',
registry,
uiOptions
);
const ArrayFieldItemTemplate = getTemplate<'ArrayFieldItemTemplate', T, S, F>(
'ArrayFieldItemTemplate',
registry,
uiOptions
);
const ArrayFieldTitleTemplate = getTemplate<'ArrayFieldTitleTemplate', T, S, F>(
'ArrayFieldTitleTemplate',
registry,
uiOptions
);
// Button templates are not overridden in the uiSchema
const {
ButtonTemplates: { AddButton },
} = registry.templates;
let gridSize = 12;
let numbered = false
if (schema.props) {
if (schema.props.hasOwnProperty("items_per_row")) {
gridSize = gridSize / schema.props.items_per_row;
}
if (schema.props.hasOwnProperty("numbered")) {
numbered = schema.props.numbered;
}
}
return (
<Paper elevation={2}>
<Box p={2}>
<ArrayFieldTitleTemplate
idSchema={idSchema}
title={uiOptions.title || title}
schema={schema}
uiSchema={uiSchema}
required={required}
registry={registry}
/>
<ArrayFieldDescriptionTemplate
idSchema={idSchema}
description={uiOptions.description || schema.description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
<Grid2 container justifyContent='flex-end'>
{items &&
items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType<T, S, F>, index) => (
<Grid2 key={key} size={gridSize} >
<Stack direction="row" sx={{alignItems: "center"}}>
{numbered &&<Typography variant="h4">{index + 1}</Typography>}
<ArrayFieldItemTemplate key={key} {...itemProps} />
</Stack>
</Grid2>
))}
</Grid2>
{canAdd && (
<Grid2 container justifyContent='flex-end'>
<Grid2>
<Box mt={2}>
<AddButton
className='array-item-add'
onClick={onAddClick}
disabled={disabled || readonly}
uiSchema={uiSchema}
registry={registry}
/>
</Box>
</Grid2>
</Grid2>
)}
</Box>
</Paper>
);
}

View File

@@ -25,9 +25,11 @@ declare module "@tiptap/core" {
export default Extension.create<IndentOptions>({ export default Extension.create<IndentOptions>({
name: "indent", name: "indent",
defaultOptions: { addOptions() {
types: ["paragraph", "heading"], return {
margin: 40 types: ["paragraph", "heading"],
margin: 40
}
}, },
addGlobalAttributes() { addGlobalAttributes() {

View File

@@ -57,7 +57,7 @@ const StyledLabelledOutlined = styled(LabelledOutlined)(({ theme }) => [{
const RichtextWidget = <T = any, S extends CrudTextRJSFSchema = CrudTextRJSFSchema, F extends FormContextType = any>( const RichtextWidget = <T = any, S extends CrudTextRJSFSchema = CrudTextRJSFSchema, F extends FormContextType = any>(
props: WidgetProps<T, S, F> props: WidgetProps<T, S, F>
) => { ) => {
const { schema, value, onChange, label, id } = props; const { schema, value, onChange, label, id, readonly } = props;
const isMultiline = schema.props.multiline === true; const isMultiline = schema.props.multiline === true;
let editorOptions: UseEditorOptions; let editorOptions: UseEditorOptions;
@@ -92,14 +92,15 @@ const RichtextWidget = <T = any, S extends CrudTextRJSFSchema = CrudTextRJSFSche
<TextContainer> <TextContainer>
<RichTextEditorProvider editor={editor}> <RichTextEditorProvider editor={editor}>
<TableBubbleMenu /> <TableBubbleMenu />
<RichTextField {!readonly && <RichTextField
controls={ controls={
<MenuControlsContainer> <MenuControlsContainer>
{isMultiline ? multilineButtons : singlelineButtons} {isMultiline ? multilineButtons : singlelineButtons}
</MenuControlsContainer> </MenuControlsContainer>
} }
variant="standard" variant="standard"
/> />}
{readonly && <RichTextField variant="standard" disabled={true}/>}
</RichTextEditorProvider> </RichTextEditorProvider>
</TextContainer> </TextContainer>
<RightContainer>&nbsp;</RightContainer> <RightContainer>&nbsp;</RightContainer>

View File

@@ -1,14 +1,68 @@
import { RJSFSchema } from '@rjsf/utils'; import { RJSFSchema } from '@rjsf/utils';
import i18n from '../../../i18n' import i18n from '../../../i18n'
import { JSONSchema7Definition } from "json-schema";
const API_URL = "/api/v1"; const API_URL = "/api/v1";
type CrudRJSFSchema = RJSFSchema & {
properties?: {
[key: string]: JSONSchema7Definition & {
readOnly?: boolean | undefined;
};
} | undefined;
}
const meta_fields = ["id", "label", "created_at", "created_by", "updated_at", "updated_by"]
export const jsonschemaProvider = { export const jsonschemaProvider = {
getResourceSchema: async (resourceName: string): Promise<RJSFSchema> => { getCardResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
const updateSchema = await getResourceSchema(`${resourceName}Update`);
const readSchema = await getResourceSchema(`${resourceName}Read`);
for (let prop_name in readSchema.properties) {
if (meta_fields.indexOf(prop_name) > -1) {
delete readSchema.properties[prop_name];
} else if (! updateSchema.hasOwnProperty(prop_name)) {
if (is_reference(readSchema.properties[prop_name])) {
let subresourceName = get_reference_name(readSchema.properties[prop_name]);
readSchema.components.schemas[subresourceName].readOnly = true;
} else {
readSchema.properties[prop_name].readOnly = true;
}
}
}
changePropertiesOrder(readSchema);
return readSchema
},
getReadOnlyResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
const updateSchema = await getResourceSchema(`${resourceName}Update`);
const readSchema = await getResourceSchema(`${resourceName}Read`);
for (let prop_name in readSchema.properties) {
if (updateSchema.hasOwnProperty(prop_name)) {
delete readSchema.properties[prop_name];
} else {
readSchema.properties[prop_name].readOnly = true;
}
}
return readSchema
},
getUpdateResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
return getResourceSchema(`${resourceName}Update`)
},
getCreateResourceSchema: async (resourceName: string): Promise<CrudRJSFSchema> => {
return getResourceSchema(`${resourceName}Create`)
},
}
const getResourceSchema = async (resourceName: string): Promise<CrudRJSFSchema> => {
return buildResource(await getJsonschema(), resourceName) return buildResource(await getJsonschema(), resourceName)
} }
};
let rawSchema: RJSFSchema; let rawSchema: RJSFSchema;
const getJsonschema = async (): Promise<RJSFSchema> => { const getJsonschema = async (): Promise<RJSFSchema> => {
@@ -24,10 +78,12 @@ function convertCamelToSnake(str: string): string {
} }
function buildResource(rawSchemas: RJSFSchema, resourceName: string) { function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
let resource; if (rawSchemas.components.schemas[resourceName] === undefined) {
throw new Error(`Resource "${resourceName}" not found in schema.`);
}
const shortResourceName = convertCamelToSnake(resourceName.replace(/(-Input|Create|Update)$/g, "")); const shortResourceName = convertCamelToSnake(resourceName.replace(/(-Input|Create|Update)$/g, ""));
resource = structuredClone(rawSchemas.components.schemas[resourceName]); let resource = structuredClone(rawSchemas.components.schemas[resourceName]);
resource.components = { schemas: {} }; resource.components = { schemas: {} };
for (let prop_name in resource.properties) { for (let prop_name in resource.properties) {
let prop = resource.properties[prop_name]; let prop = resource.properties[prop_name];
@@ -75,23 +131,35 @@ function resolveReference(rawSchemas: RJSFSchema, resource: any, prop_reference:
function changePropertiesOrder(resource: any) { function changePropertiesOrder(resource: any) {
let created_at; let created_at;
let created_by;
let updated_at; let updated_at;
let updated_by;
let new_properties: any = {}; let new_properties: any = {};
for (let prop_name in resource.properties) { for (let prop_name in resource.properties) {
if (prop_name == 'created_at') { if (prop_name == 'created_at') {
created_at = resource.properties[prop_name]; created_at = resource.properties[prop_name];
} else if (prop_name == 'created_by') {
created_by = resource.properties[prop_name];
} else if (prop_name == 'updated_at') { } else if (prop_name == 'updated_at') {
updated_at = resource.properties[prop_name]; updated_at = resource.properties[prop_name];
} else { } else if (prop_name == 'updated_by') {
updated_by = resource.properties[prop_name];
}else {
new_properties[prop_name] = resource.properties[prop_name]; new_properties[prop_name] = resource.properties[prop_name];
} }
} }
if (created_at) { if (created_at) {
new_properties['created_at'] = created_at; new_properties['created_at'] = created_at;
} }
if (created_by) {
new_properties['created_by'] = created_by;
}
if (updated_at) { if (updated_at) {
new_properties['updated_at'] = updated_at; new_properties['updated_at'] = updated_at;
} }
if (updated_by) {
new_properties['updated_by'] = updated_by;
}
resource.properties = new_properties resource.properties = new_properties
} }

View File

@@ -1,9 +1,14 @@
import { Route, Routes } from "react-router"; import { useContext } from "react";
import { Route, Routes, useParams } from "react-router";
import { useOne, useTranslation } from "@refinedev/core";
import { DeleteButton } from "@refinedev/mui";
import { CircularProgress, Stack } from "@mui/material";
import { CrudForm } from "../../lib/crud/components/crud-form";
import { FirmContext } from "../../contexts/FirmContext";
import List from "./base-page/List"; import List from "./base-page/List";
import Edit from "./base-page/Edit"; import Cartouche from "../../components/Cartouche";
import New from "./base-page/New";
type Contract = { export type Contract = {
id: string, id: string,
label: string label: string
} }
@@ -13,7 +18,6 @@ export const ContractRoutes = () => {
<Routes> <Routes>
<Route index element={ <ListContract /> } /> <Route index element={ <ListContract /> } />
<Route path="/edit/:record_id" element={ <EditContract /> } /> <Route path="/edit/:record_id" element={ <EditContract /> } />
<Route path="/create" element={ <CreateContract /> } />
</Routes> </Routes>
); );
} }
@@ -26,9 +30,39 @@ const ListContract = () => {
} }
const EditContract = () => { const EditContract = () => {
return <Edit<Contract> resource={`contracts`} schemaName={"Contract"} /> const { currentFirm } = useContext(FirmContext);
} const { translate: t } = useTranslation();
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
const { record_id } = useParams();
const CreateContract = () => { const { data, isLoading } = useOne({resource: `${resourceBasePath}/contracts`, id: record_id,});
return <New<Contract> resource={`contracts`} schemaName={"Contract"} />
if (isLoading || data?.data === undefined) {
return <CircularProgress />
}
const record = data.data;
return (
<>
<h2>{record.label}</h2>
<Cartouche record={record}/>
<CrudForm
resourceBasePath={resourceBasePath}
schemaName={"Contract"}
uiSchema={{"ui:readonly": true }}
record={record}
card={true}
>
<Stack
direction="row"
spacing={2}
sx={{
justifyContent: "flex-end",
alignItems: "center",
}}>
{ record.status == "published" && (<DeleteButton variant="contained" size="large" color="error" recordItemId={record_id}/>) }
</Stack>
</CrudForm>
</>
)
} }

View File

@@ -1,7 +1,7 @@
import { Route, Routes } from "react-router"; import { Navigate, Route, Routes, useParams } from "react-router";
import { CircularProgress } from "@mui/material"; import { CircularProgress } from "@mui/material";
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { useOne } 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";
@@ -9,6 +9,8 @@ import { FirmContext } from "../../contexts/FirmContext";
import List from "./base-page/List"; 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 dayjs from "dayjs";
type Draft = { type Draft = {
id: string, id: string,
@@ -33,7 +35,63 @@ const ListDraft = () => {
} }
const EditDraft = () => { const EditDraft = () => {
return <Edit<Draft> resource={`contracts/drafts`} schemaName={"ContractDraft"} /> const { currentFirm } = useContext(FirmContext);
const { record_id } = useParams();
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
const { data, isLoading } = useOne({
resource: `${resourceBasePath}/contracts/drafts`,
id: record_id
})
if (isLoading) {
return <CircularProgress />
}
if (!data?.data) {
return <Navigate to="../" />
}
const draft = data?.data
const readOnly = draft.status === "published";
const uiSchema = {
"ui:readonly": readOnly
}
return (
<>
<Edit<Draft> resource={"contracts/drafts"} schemaName={"ContractDraft"} uiSchema={uiSchema} />
<ContractCreate draft={draft}></ContractCreate>
</>
)
}
const ContractCreate = (props: { draft: any}) => {
const { translate: t } = useTranslation();
const { draft } = props;
if (draft.status === "published") {
return <h4>{t("resource.draft.already_published") }</h4>
}
if (draft.status === "in_progress") {
return (
<>
<h4>{ t("resource.draft.todo") + ":" }</h4>
<ul>{ draft.todo.map((item: any) => <li>{ item }</li>) }</ul>
</>
)
}
return <New<Contract>
resource={"contracts"}
schemaName={"Contract"}
defaultValue={{
date: dayjs().format("YYYY-MM-DD"),
location: "Los Santos, SA",
draft_id: draft.id
}}
uiSchema={{ draft_id: { 'ui:widget': 'hidden' } }}
/>
} }
type ForeignKeySubSchema = ForeignKeySchema & { type ForeignKeySubSchema = ForeignKeySchema & {

View File

@@ -1,4 +1,5 @@
import { Route, Routes } from "react-router"; import { Route, Routes } from "react-router";
import { useTranslation } from "@refinedev/core";
import List from "./base-page/List"; 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";
@@ -21,9 +22,10 @@ export const EntityRoutes = () => {
} }
const ListEntity = () => { const ListEntity = () => {
const { translate: t } = useTranslation();
const columns = [ const columns = [
{ field: "label", headerName: "Label", flex: 1 }, { field: "entity_data", headerName: t("schemas.type"), width: 110, valueFormatter: ({ type }: {type: string}) => type },
{ field: "entity_data", headerName: "Type", flex: 1, valueFormatter: ({ type }: {type: string}) => type } { field: "label", headerName: t("schemas.label"), flex: 1 },
]; ];
return <List<Entity> resource={`entities`} columns={columns} /> return <List<Entity> resource={`entities`} columns={columns} />
} }

View File

@@ -1,13 +1,14 @@
import { UiSchema } from "@rjsf/utils"; import { UiSchema } from "@rjsf/utils";
import { useParams } from "react-router";
import { useContext } from "react"; import { useContext } from "react";
import { Button } from "@mui/material"; import { useParams, Navigate } from "react-router";
import DeleteIcon from '@mui/icons-material/Delete'; import { Button, CircularProgress } from "@mui/material";
import Stack from "@mui/material/Stack";
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import { useForm, useTranslation } from "@refinedev/core";
import { DeleteButton } from "@refinedev/mui";
import { FirmContext } from "../../../contexts/FirmContext"; import { FirmContext } from "../../../contexts/FirmContext";
import { CrudForm } from "../../../lib/crud/components/crud-form"; import { CrudForm } from "../../../lib/crud/components/crud-form";
import Stack from "@mui/material/Stack"; import Cartouche from "../../../components/Cartouche";
import { DeleteButton } from "@refinedev/mui";
type EditProps = { type EditProps = {
resource: string, resource: string,
@@ -18,17 +19,36 @@ type EditProps = {
const Edit = <T,>(props: EditProps) => { const Edit = <T,>(props: EditProps) => {
const { schemaName, resource, uiSchema } = props; const { schemaName, resource, uiSchema } = props;
const { currentFirm } = useContext(FirmContext); const { currentFirm } = useContext(FirmContext);
const { translate: t } = useTranslation();
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}` const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
const { record_id } = useParams(); const { record_id } = useParams();
const { onFinish, query, formLoading } = useForm({
resource: `${resourceBasePath}/${resource}`,
action: "edit",
redirect: "show",
id: record_id,
});
if (formLoading) {
return <CircularProgress />
}
if (!query?.data?.data) {
return <Navigate to="../" />
}
const record = query.data.data;
return ( return (
<> <>
<h2>{record.label}</h2>
<Cartouche record={record}/>
<CrudForm <CrudForm
resourceBasePath={resourceBasePath}
schemaName={schemaName} schemaName={schemaName}
uiSchema={uiSchema} uiSchema={uiSchema}
resourceBasePath={resourceBasePath} record={record}
resource={resource} onSubmit={(data: any) => onFinish(data)}
id={record_id}
> >
<Stack <Stack
direction="row" direction="row"
@@ -37,7 +57,7 @@ const Edit = <T,>(props: EditProps) => {
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", alignItems: "center",
}}> }}>
<Button type='submit' variant="contained" size="large"><SaveIcon />Save</Button> <Button type='submit' variant="contained" size="large"><SaveIcon />{t("buttons.save")}</Button>
<DeleteButton variant="contained" size="large" color="error" recordItemId={record_id}/> <DeleteButton variant="contained" size="large" color="error" recordItemId={record_id}/>
</Stack> </Stack>
</CrudForm> </CrudForm>

View File

@@ -1,4 +1,5 @@
import { UiSchema } from "@rjsf/utils"; import { UiSchema } from "@rjsf/utils";
import { useTranslation } from "@refinedev/core";
import { List as RefineList, useDataGrid } from "@refinedev/mui"; import { List as RefineList, useDataGrid } from "@refinedev/mui";
import { DataGrid, GridColDef, GridValidRowModel } from "@mui/x-data-grid"; import { DataGrid, GridColDef, GridValidRowModel } from "@mui/x-data-grid";
import { Link, useNavigate } from "react-router" import { Link, useNavigate } from "react-router"
@@ -15,10 +16,13 @@ type ListProps<T extends GridValidRowModel> = {
const List = <T extends GridValidRowModel>(props: ListProps<T>) => { const List = <T extends GridValidRowModel>(props: ListProps<T>) => {
const { resource, columns } = props; const { resource, columns } = props;
const { translate: t } = useTranslation();
const { currentFirm } = useContext(FirmContext); const { currentFirm } = useContext(FirmContext);
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}` const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
const { dataGridProps } = useDataGrid<T>({resource: `${resourceBasePath}/${resource}`}); const { dataGridProps } = useDataGrid<T>({
resource: `${resourceBasePath}/${resource}`,
});
const navigate = useNavigate(); const navigate = useNavigate();
const cols = React.useMemo<GridColDef<T>[]>( const cols = React.useMemo<GridColDef<T>[]>(
@@ -33,12 +37,14 @@ const List = <T extends GridValidRowModel>(props: ListProps<T>) => {
return ( return (
<RefineList> <RefineList>
<Link to={"create"} > <Link to={"create"} >
<Button>Create</Button> <Button>{t("buttons.create")}</Button>
</Link> </Link>
<DataGrid <DataGrid
{...dataGridProps} {...dataGridProps}
columns={cols} columns={cols}
onRowClick={handleRowClick} /> onRowClick={handleRowClick}
pageSizeOptions={[10, 15, 20, 50, 100]}
/>
</RefineList> </RefineList>
) )
} }

View File

@@ -1,7 +1,10 @@
import { CrudForm } from "../../../lib/crud/components/crud-form";
import { UiSchema } from "@rjsf/utils"; import { UiSchema } from "@rjsf/utils";
import { useContext } from "react"; import { useContext } from "react";
import { useForm, useTranslation } from "@refinedev/core";
import { CrudForm } from "../../../lib/crud/components/crud-form";
import { FirmContext } from "../../../contexts/FirmContext"; import { FirmContext } from "../../../contexts/FirmContext";
import SaveIcon from "@mui/icons-material/Save";
import { Button } from "@mui/material";
type NewProps = { type NewProps = {
resource: string, resource: string,
@@ -13,16 +16,25 @@ type NewProps = {
const New = <T,>(props: NewProps) => { const New = <T,>(props: NewProps) => {
const { schemaName, resource, uiSchema, defaultValue } = props; const { schemaName, resource, uiSchema, defaultValue } = props;
const { currentFirm } = useContext(FirmContext); const { currentFirm } = useContext(FirmContext);
const { translate: t } = useTranslation();
const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}` const resourceBasePath = `firm/${currentFirm.instance}/${currentFirm.firm}`
const { onFinish } = useForm({
resource: `${resourceBasePath}/${resource}`,
action: "create",
redirect: "show",
});
return ( return (
<CrudForm <CrudForm
schemaName={schemaName} schemaName={schemaName}
uiSchema={uiSchema} uiSchema={uiSchema}
resourceBasePath={resourceBasePath} resourceBasePath={resourceBasePath}
resource={resource}
defaultValue={defaultValue} defaultValue={defaultValue}
/> onSubmit={(data: any) => onFinish(data)}
>
<Button type='submit' variant="contained" size="large"><SaveIcon />{t("buttons.create")}</Button>
</CrudForm>
) )
} }

View File

@@ -1,8 +1,8 @@
import { Route, Routes, Link } from "react-router"; import { Route, Routes, Link } from "react-router";
import React, { useContext } from "react"; import React, { useContext } from "react";
import { useForm, useOne, useTranslation } from "@refinedev/core";
import { FirmContext, FirmContextProvider } from "../../contexts/FirmContext"; import { FirmContext, FirmContextProvider } from "../../contexts/FirmContext";
import { Header } from "../../components"; import { Header } from "../../components";
import { useOne } from "@refinedev/core";
import { CrudForm } from "../../lib/crud/components/crud-form"; import { CrudForm } from "../../lib/crud/components/crud-form";
import { IFirm } from "../../interfaces"; import { IFirm } from "../../interfaces";
import { EntityRoutes } from "./EntityRoutes"; import { EntityRoutes } from "./EntityRoutes";
@@ -32,29 +32,18 @@ export const FirmRoutes = () => {
} }
const FirmHome = () => { const FirmHome = () => {
const { currentFirm } = useContext(FirmContext); const { translate: t } = useTranslation();
const { data: firm, isError, error, isLoading } = useOne({resource: 'firm', id: `${currentFirm.instance}/${currentFirm.firm}/`, errorNotification: false})
if (isLoading) {
return <h1>Loading...</h1>
}
if (isError && error?.statusCode == 405) {
return <FirmInitForm currentFirm={currentFirm} />
}
return ( return (
<> <>
<h1>This is la firme {currentFirm.instance} / {currentFirm.firm}</h1> <h1>{t("dashboard.title")}</h1>
<ul> <ul>
<li><Link to="entities">Entitées</Link></li> <li><Link to="entities">{t("schemas.entity.resource_plural")}</Link></li>
<li><Link to="provisions">Templates de Clauses</Link></li> <li><Link to="provisions">{t("schemas.provision_template.resource_plural")}</Link></li>
<li><Link to="templates">Templates de Contrats</Link></li> <li><Link to="templates">{t("schemas.contract_template.resource_plural")}</Link></li>
<li><Link to="drafts">Brouillons</Link></li> <li><Link to="drafts">{t("schemas.contract_draft.resource_plural")}</Link></li>
<li><Link to="contracts">Contrats</Link></li> <li><Link to="contracts">{t("schemas.contract.resource_plural")}</Link></li>
</ul> </ul>
</> </>
); );
} }
@@ -64,16 +53,26 @@ type FirmInitFormPros = {
const FirmInitForm = (props: FirmInitFormPros) => { const FirmInitForm = (props: FirmInitFormPros) => {
const { currentFirm } = props; const { currentFirm } = props;
const { translate: t } = useTranslation();
const resourceBasePath = `firm`
const { onFinish } = useForm({
resource: `${resourceBasePath}/${currentFirm.instance}/${currentFirm.firm}`,
action: "create",
redirect: "show",
});
return ( return (
<> <>
<h1>Initialization of {`${currentFirm.instance} / ${currentFirm.firm}`}</h1> <h1>Initialization of {`${currentFirm.instance} / ${currentFirm.firm}`}</h1>
<CrudForm <CrudForm
schemaName={"CurrentFirmSchemaCreate"} schemaName={"CurrentFirmSchema"}
resource={`firm/${currentFirm.instance}/${currentFirm.firm}/`} resourceBasePath={resourceBasePath}
defaultValue={{corporation: {entity_data: {activity: t("firm.default_activity") }}}}
uiSchema={{ uiSchema={{
corporation: {entity_data: {employees: {"ui:style": {"display": "none"}}}}, corporation: {entity_data: {employees: {"ui:style": {"display": "none"}}}},
}} }}
onSubmit={(data: any) => onFinish(data)}
/> />
</> </>
) )

View File

@@ -1,6 +1,6 @@
import { useInvalidateAuthStore } from "@refinedev/core"; import { useForm, useInvalidateAuthStore } from "@refinedev/core";
import { CrudForm } from "../../lib/crud/components/crud-form"; import { CrudForm } from "../../lib/crud/components/crud-form";
import {empty_user} from "../../providers/auth-provider"; import { empty_user } from "../../providers/auth-provider";
export const CreateFirm = () => { export const CreateFirm = () => {
const invalidateAuthStore = useInvalidateAuthStore() const invalidateAuthStore = useInvalidateAuthStore()
@@ -9,11 +9,19 @@ export const CreateFirm = () => {
invalidateAuthStore().then(); invalidateAuthStore().then();
} }
const resourceBasePath = "hub/users";
const { onFinish } = useForm({
resource: `${resourceBasePath}/firms`,
action: "create",
redirect: "list",
onMutationSuccess: data => refreshUser()
});
return ( return (
<CrudForm <CrudForm
schemaName={"FirmCreate"} schemaName={"Firm"}
resource={"hub/users/firms/"} resourceBasePath={resourceBasePath}
onSuccess={() => { refreshUser() }} onSubmit={(data: any) => onFinish(data)}
/> />
) )
} }