28 Commits

Author SHA1 Message Date
2948e9b961 Adding metadata for social networks on signature page 2023-03-19 13:55:57 +01:00
191a3d0018 Adding a label to signature read entities 2023-03-19 13:55:13 +01:00
8885969a07 Removing nickname from individual's labels 2023-03-19 13:54:38 +01:00
a997e54891 Adding an autogenerated label to contracts 2023-03-19 13:53:07 +01:00
0b12f8718a Decorating the sidenav 2023-03-18 18:29:37 +01:00
d5d2f31178 Correcting signature translation bug 2023-03-18 17:35:06 +01:00
1bd2774cdd Correcting redirection error after contract creation 2023-03-18 17:27:35 +01:00
e5375edda8 Changing website title 2023-03-18 17:20:08 +01:00
ae306bb89e Correcting overselecting menue 2023-03-18 17:13:36 +01:00
e877c36a3d Moving contracts/drafts to contract-drafts 2023-03-18 17:07:29 +01:00
7f639fc5a0 Correcting overselecting menue 2023-03-18 17:05:58 +01:00
7fbba953cd separating logoutbutton from rest of menu 2023-03-18 16:28:18 +01:00
4e52999b5d Progression translations 2023-03-18 16:21:55 +01:00
392d66b03e Correcting bug caused by not deep enough clones 2023-03-18 16:12:54 +01:00
dc6616bba6 Use address bar to remember list params 2023-03-17 17:45:50 +01:00
f3f0ddc004 Implementing enus is jsonschema utilities 2023-03-17 17:39:20 +01:00
0b9d5d42cb Increasing flashmessage display duration to human-readable length 2023-03-17 15:23:58 +01:00
bb0ecb5c13 Sending an error message on contract failure 2023-03-17 15:23:21 +01:00
5a5f1b3519 Correcting foreignKey not updated on update 2023-03-17 15:15:53 +01:00
2adecb99d2 Removing htmltags in list values 2023-03-17 15:06:31 +01:00
3db7c62b09 Filtering out htmlentities in rich text field 2023-03-17 14:36:40 +01:00
78c4ee119a Improving table list display 2023-03-17 14:35:55 +01:00
46ac3295e3 Merge branch 'master' of git.dorfsvald.net:ewandor/cht-lawfirm 2023-03-16 15:36:53 +01:00
2349f4c804 Adding a makefile 2023-03-16 15:36:36 +01:00
334150bc0f Preparing dockers for prod 2023-03-16 15:03:06 +01:00
ewandor
da19ef652e Merge branch 'master' of git.dorfsvald.net:ewandor/cht-lawfirm 2023-03-16 03:47:47 +01:00
ewandor
015fc00f6f using root when locating assets in contract print 2023-03-16 03:47:29 +01:00
e22c197d3a Involving gittea image registry 2023-03-16 02:55:55 +01:00
36 changed files with 485 additions and 119 deletions

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
publish:
git checkout $(TAG)
docker build -f back/Dockerfile.prod -t git.dorfsvald.net/ewandor/cht-lawfirm-back-prod back
docker tag git.dorfsvald.net/ewandor/cht-lawfirm-back-prod:latest git.dorfsvald.net/ewandor/cht-lawfirm-back-prod:$(TAG)
docker push git.dorfsvald.net/ewandor/cht-lawfirm-back-prod:latest
docker push git.dorfsvald.net/ewandor/cht-lawfirm-back-prod:$(TAG)
docker build -f front/Dockerfile.prod -t git.dorfsvald.net/ewandor/cht-lawfirm-nginx-prod front
docker tag git.dorfsvald.net/ewandor/cht-lawfirm-nginx-prod:latest git.dorfsvald.net/ewandor/cht-lawfirm-nginx-prod:$(TAG)
docker push git.dorfsvald.net/ewandor/cht-lawfirm-nginx-prod:latest
docker push git.dorfsvald.net/ewandor/cht-lawfirm-nginx-prod:$(TAG)
git switch -

View File

@@ -5,13 +5,10 @@ RUN apt update && apt install -y xfonts-base xfonts-75dpi python3-pip python3-cf
WORKDIR /code WORKDIR /code
# copy both 'package.json' and 'package-lock.json' (if available)
COPY ./requirements.txt /code/requirements.txt COPY ./requirements.txt /code/requirements.txt
# install project dependencies
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
# copy project files and folders to the current working directory (i.e. 'app' folder)
COPY ./app /code/app COPY ./app /code/app
EXPOSE 8000 EXPOSE 8000

15
back/Dockerfile.prod Normal file
View File

@@ -0,0 +1,15 @@
FROM python:3.10
RUN apt update && apt install -y xfonts-base xfonts-75dpi python3-pip python3-cffi python3-brotli libpango-1.0-0 libpangoft2-1.0-0 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./app /code/app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -7,7 +7,7 @@ from .routes_draft import draft_router
from .print import print_router from .print import print_router
from .models import Contract, ContractDraft, ContractDraftStatus, Party, replace_variables_in_value from .models import Contract, ContractDraft, ContractDraftStatus, Party, replace_variables_in_value
from .schemas import ContractCreate, ContractRead, ContractUpdate from .schemas import ContractCreate, ContractRead, ContractUpdate, SignatureRead
from ..entity.models import Entity from ..entity.models import Entity
from ..template.models import ProvisionTemplate from ..template.models import ProvisionTemplate
@@ -75,10 +75,12 @@ async def update(id: str, contract_form: ContractUpdate, user=Depends(get_curren
@contract_router.get("/signature/{signature_id}", response_description="") @contract_router.get("/signature/{signature_id}", response_description="")
async def get_signature(signature_id: str) -> Party: async def get_signature(signature_id: str) -> SignatureRead:
contract = await Contract.find_by_signature_id(signature_id) contract = await Contract.find_by_signature_id(signature_id)
signature = contract.get_signature(signature_id) signature = contract.get_signature(signature_id)
return signature signature_dict = signature.dict()
signature_dict['contract_label'] = contract.label
return signature_dict
@contract_router.post("/signature/{signature_id}", response_description="") @contract_router.post("/signature/{signature_id}", response_description="")

View File

@@ -2,7 +2,7 @@ import datetime
from typing import List, Literal from typing import List, Literal
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, validator
from beanie.operators import ElemMatch from beanie.operators import ElemMatch
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
@@ -90,7 +90,7 @@ class DraftProvision(BaseModel):
provision: ContractProvisionTemplateReference | ProvisionGenuine = Field(..., discriminator='type') provision: ContractProvisionTemplateReference | ProvisionGenuine = Field(..., discriminator='type')
class Config: class Config:
title = 'Provision' title = 'Clause'
class Provision(BaseModel): class Provision(BaseModel):
@@ -166,6 +166,10 @@ class ContractDraft(CrudDocument):
class Contract(CrudDocument): class Contract(CrudDocument):
"""
Contrat publié. Les contrats ne peuvent pas être modifiés.
Ils peuvent seulement être signés par les parties et imprimés par l'avocat
"""
name: str = Field(title="Nom") name: str = Field(title="Nom")
title: str = Field(title="Titre") title: str = Field(title="Titre")
parties: List[Party] = Field(title="Parties") parties: List[Party] = Field(title="Parties")
@@ -177,6 +181,19 @@ class Contract(CrudDocument):
lawyer: Entity = Field(title="Avocat en charge") lawyer: Entity = Field(title="Avocat en charge")
location: str = Field(title="Lieu") location: str = Field(title="Lieu")
date: datetime.date = Field(title="Date") date: datetime.date = Field(title="Date")
label: str = None
@validator("label", always=True)
def generate_label(cls, v, values, **kwargs):
if not v:
contract_label = values['title']
for p in values['parties']:
contract_label = contract_label + f" - {p.entity.label}"
contract_label = contract_label + f" {values['date'].strftime('%m/%d/%Y')}"
return contract_label
return v
class Settings(CrudDocument.Settings): class Settings(CrudDocument.Settings):
fulltext_search = ['name', 'title'] fulltext_search = ['name', 'title']

View File

@@ -81,7 +81,7 @@ async def render_css(root_url, contract):
async def preview_draft(draft_id: str, request: Request) -> str: async def preview_draft(draft_id: str, request: Request) -> str:
draft = await build_model(await ContractDraft.get(draft_id)) draft = await build_model(await ContractDraft.get(draft_id))
return await render_print(f'{request.url.scheme}://{request.url.hostname}', draft) return await render_print('', draft)
@print_router.get("/preview/signature/{signature_id}", response_class=HTMLResponse) @print_router.get("/preview/signature/{signature_id}", response_class=HTMLResponse)
@@ -91,7 +91,7 @@ async def preview_contract_by_signature(signature_id: str, request: Request) ->
if p.signature_affixed: if p.signature_affixed:
p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png') p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png')
return await render_print(f'{request.url.scheme}://{request.url.hostname}', contract) return await render_print('', contract)
@print_router.get("/preview/{contract_id}", response_class=HTMLResponse) @print_router.get("/preview/{contract_id}", response_class=HTMLResponse)
@@ -101,7 +101,7 @@ async def preview_contract(contract_id: str, request: Request) -> str:
if p.signature_affixed: if p.signature_affixed:
p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png') p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png')
return await render_print(f'{request.url.scheme}://{request.url.hostname}', contract) return await render_print('', contract)
@print_router.get("/pdf/{contract_id}", response_class=FileResponse) @print_router.get("/pdf/{contract_id}", response_class=FileResponse)

View File

@@ -73,3 +73,12 @@ class ContractCreate(Writer):
class ContractUpdate(BaseModel): class ContractUpdate(BaseModel):
pass pass
class SignatureRead(BaseModel):
signature_uuid: str
entity: Entity
part: str
representative: Entity = None
signature_affixed: bool
contract_label: str

View File

@@ -28,8 +28,8 @@ class Individual(EntityType):
@property @property
def label(self) -> str: def label(self) -> str:
if len(self.surnames) > 0: # if len(self.surnames) > 0:
return '{} "{}" {}'.format(self.firstname, self.surnames[0], self.lastname) # return '{} "{}" {}'.format(self.firstname, self.surnames[0], self.lastname)
return '{} {}'.format(self.firstname, self.lastname) return '{} {}'.format(self.firstname, self.lastname)

View File

@@ -1,19 +1,13 @@
version: "3.9" version: "3.9"
services: services:
back: back:
build: image: git.dorfsvald.net/ewandor/cht-lawfirm-back-prod:latest
context: ${ROOT_PATH}/back
image: cht-lawfirm-back-prod
restart: always restart: always
volumes: volumes:
- ${ROOT_PATH}/back/app:/code/app
- ${ROOT_PATH}/back/media:/code/media - ${ROOT_PATH}/back/media:/code/media
nginx: nginx:
build: image: git.dorfsvald.net/ewandor/cht-lawfirm-nginx-prod:latest
context: ${ROOT_PATH}/front
dockerfile: prod.Dockerfile
image: cht-lawfirm-nginx-prod
restart: always restart: always
ports: ports:
- "3820:80" - "3820:80"

View File

@@ -0,0 +1,42 @@
version: "3.9"
services:
back:
build:
context: ${ROOT_PATH}/back
restart: always
ports:
- "8000:8000"
volumes:
- ${ROOT_PATH}/back/app:/code/app
- ${ROOT_PATH}/back/media:/code/media
front:
build:
context: ${ROOT_PATH}/front
restart: always
ports:
- "4200:4200"
volumes:
- ${ROOT_PATH}/front/app/src:/app/src
- ${ROOT_PATH}/front/app/public:/app/public
nginx:
build:
context: ${ROOT_PATH}/nginx
restart: always
ports:
- "3820:80"
mongo:
image: "mongo:4.4.18"
restart: always
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: IBO3eber0mdw2R9pnInLdtFykQFY2f06
volumes:
- database:/data/db
volumes:
database:

View File

@@ -1,6 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import {ContractsModule} from "./views/contracts/contracts.module";
const routes: Routes = [ const routes: Routes = [
{ {
@@ -30,6 +29,11 @@ const routes: Routes = [
loadChildren: () => loadChildren: () =>
import('./views/templates/templates.module').then((m) => m.TemplatesModule) import('./views/templates/templates.module').then((m) => m.TemplatesModule)
}, },
{
path: 'contract-drafts',
loadChildren: () =>
import('./views/contract-drafts/contract-drafts.module').then((m) => m.ContractDraftsModule)
},
{ {
path: 'contracts', path: 'contracts',
loadChildren: () => loadChildren: () =>

View File

@@ -0,0 +1,5 @@
.sidenav {
background-image: url("/assets/leather_texture.png");
background-size: 220px;
max-width: 220px;
}

View File

@@ -3,7 +3,7 @@
<main style="margin-top: 0px;"> <main style="margin-top: 0px;">
<div class="container-fluid"> <div class="container-fluid">
<div class="row flex-nowrap"> <div class="row flex-nowrap">
<div class="col-auto col-md-3 col-xl-2 px-sm-2 px-0 bg-dark" style="max-width: 200px;"> <div class="sidenav col-auto col-md-3 col-xl-2 px-sm-2 px-0 bg-dark">
<sidenav class="sticky-top"></sidenav> <sidenav class="sticky-top"></sidenav>
</div> </div>
<div class="col py-3"> <div class="col py-3">

View File

@@ -1,4 +1,5 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import {Title} from "@angular/platform-browser";
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -8,5 +9,7 @@ import { Component } from '@angular/core';
export class AppComponent { export class AppComponent {
title = 'Cooper, Hillman & Toshi'; title = 'Cooper, Hillman & Toshi';
constructor() {} constructor(private titleService: Title) {
titleService.setTitle(this.title)
}
} }

View File

@@ -1,7 +1,7 @@
<div class="toast-container position-fixed bottom-0 end-0 p-3"> <div class="toast-container position-fixed bottom-0 end-0 p-3">
<ngb-toast <ngb-toast
*ngFor="let flashmessage of flashmessagesService.toasts" *ngFor="let flashmessage of flashmessagesService.toasts"
[header]="flashmessage.type" [autohide]="true" [delay]="flashmessage.delay || 1500" [header]="flashmessage.type" [autohide]="true" [delay]="flashmessage.delay || 5000"
(hiddden)="flashmessagesService.remove(flashmessage)" (hiddden)="flashmessagesService.remove(flashmessage)"
> >
<ng-container [ngSwitch]="flashmessage.type"> <ng-container [ngSwitch]="flashmessage.type">

View File

@@ -0,0 +1,3 @@
.logout {
margin-top: 25px;
}

View File

@@ -11,6 +11,6 @@
</a> </a>
</li> </li>
</ng-container> </ng-container>
<li><logout></logout></li> <li class="logout"><logout></logout></li>
</ul> </ul>
</div> </div>

View File

@@ -3,13 +3,21 @@ import { Router } from '@angular/router';
import { IconNamesEnum } from "ngx-bootstrap-icons"; import { IconNamesEnum } from "ngx-bootstrap-icons";
import { AuthService } from "../auth/auth.service"; import { AuthService } from "../auth/auth.service";
interface MenuItem {
title: string,
link: string,
icon: IconNamesEnum
}
@Component({ @Component({
selector: "sidenav", selector: "sidenav",
templateUrl: "./sidenav.component.html", templateUrl: "./sidenav.component.html",
styleUrls: ["./sidenav.component.css"] styleUrls: ["./sidenav.component.css"]
}) })
export class SidenavComponent { export class SidenavComponent {
Menu = [ Menu: MenuItem[] = [
{ {
title: $localize`Dashboard`, title: $localize`Dashboard`,
link: "/dashboard", link: "/dashboard",
@@ -32,7 +40,7 @@ export class SidenavComponent {
}, },
{ {
title: $localize`Contracts&nbsp;Drafts`, title: $localize`Contracts&nbsp;Drafts`,
link: "/contracts/drafts", link: "/contract-drafts",
icon: IconNamesEnum.PencilSquare icon: IconNamesEnum.PencilSquare
}, },
{ {
@@ -54,7 +62,7 @@ export class SidenavComponent {
}) })
} }
is_current_page(menu_item: any) { is_current_page(menu_item: MenuItem) {
return this.router.url.indexOf(menu_item.link) > -1; return this.router.url.startsWith(menu_item.link);
} }
} }

View File

@@ -0,0 +1,44 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DraftsCardComponent, DraftsListComponent, DraftsNewComponent } from "./drafts.component";
const routes: Routes = [
{
path: '',
data: {
title: 'Contract Drafts',
},
children: [
{ path: '', redirectTo: 'list', pathMatch: 'full' },
{
path: 'list',
component: DraftsListComponent,
data: {
title: 'List',
},
},
{
path: 'new',
component: DraftsNewComponent,
data: {
title: 'New',
},
},
{
path: ':id',
component: DraftsCardComponent,
data: {
title: 'Card',
},
},
],
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ContractDraftsRoutingModule {}

View File

@@ -0,0 +1,42 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BaseViewModule } from "../base-view/base-view.module";
import { ContractDraftsRoutingModule } from './contract-drafts-routing.module';
import { DraftsCardComponent, DraftsListComponent, DraftsNewComponent, DraftsNewFormComponent } from "./drafts.component";
import { FormlyModule } from "@ngx-formly/core";
import { FormlyBootstrapModule } from "@ngx-formly/bootstrap";
import { ForeignkeyTypeComponent } from "@common/crud/types/foreignkey.type";
import { CrudService, ImageUploaderCrudService } from "@common/crud/crud.service";
import { NgbAccordionModule, NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
import { allIcons, NgxBootstrapIconsModule } from "ngx-bootstrap-icons";
import { ClipboardModule } from "@angular/cdk/clipboard";
@NgModule({
imports: [
CommonModule,
BaseViewModule,
ContractDraftsRoutingModule,
NgbAccordionModule,
NgbCollapseModule,
NgxBootstrapIconsModule.pick(allIcons),
FormlyModule.forRoot({
types: [
{ name: 'foreign-key', component: ForeignkeyTypeComponent }
]
}),
FormlyBootstrapModule,
ClipboardModule,
],
declarations: [
DraftsListComponent,
DraftsNewComponent,
DraftsCardComponent,
DraftsNewFormComponent
],
providers: [CrudService, ImageUploaderCrudService]
})
export class ContractDraftsModule {
}

View File

@@ -6,6 +6,7 @@ import { CrudService } from "@common/crud/crud.service";
import { ActivatedRoute, ParamMap, Router } from "@angular/router"; import { ActivatedRoute, ParamMap, Router } from "@angular/router";
import { formatDate } from "@angular/common"; import { formatDate } from "@angular/common";
import {FlashmessagesService} from "../../layout/flashmessages/flashmessages.service";
export class BaseDraftsComponent { export class BaseDraftsComponent {
@@ -97,10 +98,10 @@ export class DraftsNewComponent extends BaseDraftsComponent implements OnInit {
(resourceReceived)="this.onResourceReceived($event)" (resourceReceived)="this.onResourceReceived($event)"
> >
</base-card> </base-card>
<a class="btn btn-link" href="/api/v1/contract/print/preview/draft/{{this.resource_id}}" target="_blank">Preview</a> <a class="btn btn-link" href="/api/v1/contract/print/preview/draft/{{this.resource_id}}" target="_blank" i18n>Preview</a>
<ng-container *ngIf="this.isReadyForPublication;"> <ng-container *ngIf="this.isReadyForPublication;">
<formly-form [fields]="newContractFormfields" [form]="newContractForm" [model]="newContractModel"></formly-form> <formly-form [fields]="newContractFormfields" [form]="newContractForm" [model]="newContractModel"></formly-form>
<button class="btn btn-success" (click)="publish()">Publish</button> <button class="btn btn-success" (click)="publish()" i18n>Publish</button>
</ng-container> </ng-container>
` `
}) })
@@ -142,6 +143,7 @@ export class DraftsCardComponent extends BaseDraftsComponent implements OnInit {
private formlyJsonschema: CrudFormlyJsonschemaService, private formlyJsonschema: CrudFormlyJsonschemaService,
private crudService: CrudService, private crudService: CrudService,
private router: Router, private router: Router,
private flashService: FlashmessagesService,
) { ) {
super(); super();
} }
@@ -159,8 +161,9 @@ export class DraftsCardComponent extends BaseDraftsComponent implements OnInit {
} }
publish() { publish() {
this.crudService.create('contract', this.newContractModel).subscribe((response: any) => { this.crudService.create('contract', this.newContractModel).subscribe({
this.router.navigate([`../../${response.id}`], {relativeTo: this.route}); next: (response: any) => this.router.navigate([`../../contracts/${response.id}`], {relativeTo: this.route}),
error: (err) => this.flashService.error(err)
}); });
} }

View File

@@ -1,7 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { DraftsCardComponent, DraftsListComponent, DraftsNewComponent } from "./drafts.component";
import { ContractsCardComponent, ContractsListComponent, ContractsNewComponent, ContractsSignatureComponent} from "./contracts.component"; import { ContractsCardComponent, ContractsListComponent, ContractsNewComponent, ContractsSignatureComponent} from "./contracts.component";
@@ -13,6 +12,7 @@ const routes: Routes = [
}, },
children: [ children: [
{ path: '', redirectTo: 'list', pathMatch: 'full' }, { path: '', redirectTo: 'list', pathMatch: 'full' },
{ path: 'drafts', redirectTo: '/contract-drafts/list' },
{ {
path: 'list', path: 'list',
component: ContractsListComponent, component: ContractsListComponent,
@@ -34,36 +34,6 @@ const routes: Routes = [
title: 'New', title: 'New',
}, },
}, },
{
path: 'drafts',
data: {
title: 'Drafts',
},
children: [
{ path: '', redirectTo: 'list', pathMatch: 'full' },
{
path: 'list',
component: DraftsListComponent,
data: {
title: 'List',
},
},
{
path: 'new',
component: DraftsNewComponent,
data: {
title: 'New',
},
},
{
path: ':id',
component: DraftsCardComponent,
data: {
title: 'Card',
},
},
],
},
{ {
path: ':id', path: ':id',
component: ContractsCardComponent, component: ContractsCardComponent,

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, ElementRef, Input, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from "@angular/router"; import { ActivatedRoute, ParamMap } from "@angular/router";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer, Meta } from "@angular/platform-browser";
import { ImageUploaderCrudService } from "@common/crud/crud.service"; import { ImageUploaderCrudService } from "@common/crud/crud.service";
@@ -81,7 +81,7 @@ export class ContractsCardComponent extends BaseContractsComponent{
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-1"> <ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-1">
<ngb-panel> <ngb-panel>
<ng-template ngbPanelTitle> <ng-template ngbPanelTitle>
<span>Preview</span> <span i18n>Preview</span>
</ng-template> </ng-template>
<ng-template ngbPanelContent> <ng-template ngbPanelContent>
<iframe width="100%" <iframe width="100%"
@@ -91,19 +91,14 @@ export class ContractsCardComponent extends BaseContractsComponent{
</ngb-panel> </ngb-panel>
<ngb-panel> <ngb-panel>
<ng-template ngbPanelTitle> <ng-template ngbPanelTitle>
<span>Signature</span> <span i18n>Signature</span>
</ng-template> </ng-template>
<ng-template ngbPanelContent> <ng-template ngbPanelContent>
<ng-container *ngIf="this.affixed">This Contract has already been signed by {{ this.signatory }}</ng-container> <ng-container *ngIf="this.affixed"><ng-container i18n>This Contract has already been signed by</ng-container> {{ this.signatory }}</ng-container>
<div class="row" *ngIf="!this.affixed"> <div class="row" *ngIf="!this.affixed">
<signature-drawer class="col-7" <signature-drawer class="col-7"
(signatureDrawn$)="postSignature($event)"></signature-drawer> (signatureDrawn$)="postSignature($event)"></signature-drawer>
<div class="col-5"> <div class="col-5" i18n [innerHTML]="this.legalText"></div>
<p>Cette page est à la destination exclusive de <strong>{{ this.signatory }}</strong></p>
<p>Si vous n'êtes <strong>pas</strong> {{ this.signatory }}, veuillez <strong>fermer cette page immédiatement</strong> et surpprimer tous les liens en votre possession menant vers celle-ci.</p>
<p>En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour <strong>usurpation d'identité</strong> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.</p>
<p>Le cabinet Cooper, Hillman & Toshi LLC</p>
</div>
</div> </div>
</ng-template> </ng-template>
</ngb-panel> </ngb-panel>
@@ -117,10 +112,13 @@ export class ContractsSignatureComponent implements OnInit {
affixed = false; affixed = false;
public legalText: string = "";
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private crudService: ImageUploaderCrudService, private crudService: ImageUploaderCrudService,
private meta: Meta,
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -134,8 +132,26 @@ export class ContractsSignatureComponent implements OnInit {
} else { } else {
this.signatory = this.signature.entity.entity_data.firstname + " " + this.signature.entity.entity_data.lastname; this.signatory = this.signature.entity.entity_data.firstname + " " + this.signature.entity.entity_data.lastname;
} }
})
}) this.legalText = `
<p>Cette page est à la destination exclusive de <strong>${ this.signatory }</strong></p>
<p>Si vous n'êtes <strong>pas</strong> ${ this.signatory }, veuillez <strong>fermer cette page immédiatement</strong> et surpprimer tous les liens en votre possession menant vers celle-ci.</p>
<p>En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour <strong>usurpation d'identité</strong> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.</p>
<p>Le cabinet Cooper, Hillman & Toshi LLC</p>
`
this.meta.updateTag({
name: 'og:title',
property: 'og:title',
content: this.signature.contract_label});
this.meta.updateTag({
name: 'og:description',
property: 'og:description',
content: this.legalText.replace(/<[^>]*>/g, '').trim()
});
this.meta.updateTag({ name: 'og:image', content: `${location.origin}/assets/logo.png` });
});
});
} }
getPreview() { getPreview() {

View File

@@ -3,7 +3,6 @@ import { NgModule } from '@angular/core';
import { BaseViewModule } from "../base-view/base-view.module"; import { BaseViewModule } from "../base-view/base-view.module";
import { ContractsRoutingModule } from './contracts-routing.module'; import { ContractsRoutingModule } from './contracts-routing.module';
import { DraftsCardComponent, DraftsListComponent, DraftsNewComponent, DraftsNewFormComponent } from "./drafts.component";
import { FormlyModule } from "@ngx-formly/core"; import { FormlyModule } from "@ngx-formly/core";
import { FormlyBootstrapModule } from "@ngx-formly/bootstrap"; import { FormlyBootstrapModule } from "@ngx-formly/bootstrap";
import { ForeignkeyTypeComponent } from "@common/crud/types/foreignkey.type"; import { ForeignkeyTypeComponent } from "@common/crud/types/foreignkey.type";
@@ -33,10 +32,6 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
ClipboardModule, ClipboardModule,
], ],
declarations: [ declarations: [
DraftsListComponent,
DraftsNewComponent,
DraftsCardComponent,
DraftsNewFormComponent,
ContractsListComponent, ContractsListComponent,
ContractsNewComponent, ContractsNewComponent,
ContractsCardComponent, ContractsCardComponent,

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -112,10 +112,10 @@ export class CardComponent implements OnInit {
model._id = this.resource_id; model._id = this.resource_id;
this.crudService.update(this.resource!, model).subscribe( { this.crudService.update(this.resource!, model).subscribe( {
next: (model: any) => { next: (model: any) => {
this.model = model;
this._modelLoading$.next(false);
this.resourceUpdated.emit(model._id); this.resourceUpdated.emit(model._id);
this.resourceReceived.emit(model); this.resourceReceived.emit(model);
this.model = model;
this._modelLoading$.next(false);
}, },
error: (err) => this.error.emit("Error updating the entity:" + err) error: (err) => this.error.emit("Error updating the entity:" + err)
}); });

View File

@@ -30,7 +30,7 @@ export class JsonschemasService {
buildResource(resourceName: string) { buildResource(resourceName: string) {
let resource; let resource;
resource = { ... this.rawSchemas.components.schemas[resourceName]}; resource = structuredClone(this.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];
@@ -162,6 +162,14 @@ export class JsonschemasService {
} }
return false; return false;
} }
} else if (this.is_enum(resource)) {
for (const ref of resource.allOf!) {
// @ts-ignore
if (this.has_descendant(ref, property_name)) {
return true;
}
return false;
}
} }
throw new Error("Jsonschema format not implemented in property finder"); throw new Error("Jsonschema format not implemented in property finder");
return false; return false;
@@ -186,6 +194,14 @@ export class JsonschemasService {
return this.get_descendant(ref, property_name); return this.get_descendant(ref, property_name);
} }
} }
} else if (this.is_enum(resource)) {
for (const ref of resource.allOf!) {
// @ts-ignore
if (this.has_descendant(ref, property_name)) {
// @ts-ignore
return this.get_descendant(ref, property_name);
}
}
} }
throw new Error("property not found or Jsonschema format not implemented"); throw new Error("property not found or Jsonschema format not implemented");
} }

View File

@@ -14,13 +14,14 @@ import {CrudFormlyJsonschemaService} from "@common/crud/crud-formly-jsonschema.s
export class FilterListComponent implements OnInit { export class FilterListComponent implements OnInit {
@Input() filters: string[] = []; @Input() filters: string[] = [];
@Input() schema = ""; @Input() schema = "";
@Input() values = {};
@Output() filterChange: EventEmitter<{[key: string]: any}> = new EventEmitter(); @Output() filterChange: EventEmitter<{[key: string]: any}> = new EventEmitter();
form = new FormGroup({}); form = new FormGroup({});
fields: FormlyFieldConfig[] = []; fields: FormlyFieldConfig[] = [];
searchTerms = {} searchTerms: {[key: string]: string | {}} = {}
public fieldJson = { public fieldJson = {
components: {}, components: {},
@@ -48,6 +49,16 @@ export class FilterListComponent implements OnInit {
prop = schema.components.schemas[prop.allOf![0]['$ref'].replace('#/components/schemas/', '')]; prop = schema.components.schemas[prop.allOf![0]['$ref'].replace('#/components/schemas/', '')];
prop.type = "array"; prop.type = "array";
prop.items = {"type": "string", "enum": prop.enum}; prop.items = {"type": "string", "enum": prop.enum};
if (filter in this.values) {
this.searchTerms[filter] = {};
// @ts-ignore
for (let val of this.values[filter]) {
// @ts-ignore
this.searchTerms[filter][val] = true;
}
}
} else if(true) {
} }
if (prop.hasOwnProperty('readOnly') && prop.readOnly) { if (prop.hasOwnProperty('readOnly') && prop.readOnly) {

View File

@@ -0,0 +1,3 @@
.table-row-link {
cursor: pointer;
}

View File

@@ -14,20 +14,25 @@
/> />
</div> </div>
<div class="col-xs-3 col-sm-auto"> <div class="col-xs-3 col-sm-auto">
<crud-list-filter-list [filters]="this.filters" [schema]="this.schema!" (filterChange)="onFilterChange($event)"></crud-list-filter-list> <crud-list-filter-list
[filters]="this.filters"
[schema]="this.schema!"
[values]="this.searchFilters"
(filterChange)="onFilterChange($event)"
></crud-list-filter-list>
</div> </div>
<span class="col col-form-label" i18n *ngIf="loading$ | async">Loading...</span> <span class="col col-form-label" i18n *ngIf="loading$ | async">Loading...</span>
</div> </div>
<div class="table-responsive-md"> <div class="table-responsive-md">
<table class="table table-striped"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th *ngFor="let col of this.displayedColumns" scope="col" sortable="name" (sort)="onSort($event)">{{ col.title }}</th> <th *ngFor="let col of this.displayedColumns" scope="col" sortable="name" (sort)="onSort($event)">{{ col.title }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let row of listData$ | async" (click)="onSelect(row._id)"> <tr *ngFor="let row of listData$ | async" (click)="onRowClick(row._id)" (auxclick)="onRowMiddleClick(row._id);" class="table-row-link">
<td *ngFor="let col of this.displayedColumns"> <td class="text-truncate" *ngFor="let col of this.displayedColumns" style="max-width: 150px;">
<ngb-highlight [result]="getColumnValue(row, col.path)" [term]="searchTerm"></ngb-highlight> <ngb-highlight [result]="getColumnValue(row, col.path)" [term]="searchTerm"></ngb-highlight>
</td> </td>
</tr> </tr>

View File

@@ -40,7 +40,6 @@ export class ListComponent implements OnInit {
public displayedColumns: Column[] = []; public displayedColumns: Column[] = [];
private _loading$ = new BehaviorSubject<boolean>(true); private _loading$ = new BehaviorSubject<boolean>(true);
//private _search$ = new Subject<void>();
private _listData$ = new BehaviorSubject<any[]>([]); private _listData$ = new BehaviorSubject<any[]>([]);
private _total$ = new BehaviorSubject<number>(0); private _total$ = new BehaviorSubject<number>(0);
@@ -64,7 +63,14 @@ export class ListComponent implements OnInit {
next: (schema: any) => this.getColumnDefinition(schema), next: (schema: any) => this.getColumnDefinition(schema),
error: (err) => this.error.emit("Error loading the schema:" + err) error: (err) => this.error.emit("Error loading the schema:" + err)
}); });
this._search(); this.route.queryParams.subscribe(params => {
let parsedParams = {...params};
if (parsedParams.hasOwnProperty('searchFilters')) {
parsedParams['searchFilters'] = JSON.parse(parsedParams['searchFilters']);
}
this._total$.next(this.page * this.pageSize);
this._set(parsedParams)
});
} }
getColumnDefinition(schema: JSONSchema7) { getColumnDefinition(schema: JSONSchema7) {
@@ -100,7 +106,7 @@ export class ListComponent implements OnInit {
parent = parent[key]; parent = parent[key];
} }
} }
return parent; return parent.replace(/<[^>]*>/g, '');
} }
private _search() { private _search() {
@@ -144,10 +150,15 @@ export class ListComponent implements OnInit {
this.sortDirection = direction; this.sortDirection = direction;
} }
onSelect(id: string) { onRowClick(id: string) {
this.router.navigate([`../${id}`], {relativeTo: this.route}); this.router.navigate([`../${id}`], {relativeTo: this.route});
} }
onRowMiddleClick(id: string) {
let newUrl = window.location.href.replace('list', id).split('?')[0]
window.open(newUrl, '_blank');
}
onCreate() { onCreate() {
this.router.navigate([`../new`], {relativeTo: this.route}); this.router.navigate([`../new`], {relativeTo: this.route});
} }
@@ -177,30 +188,43 @@ export class ListComponent implements OnInit {
return this._state.searchTerm; return this._state.searchTerm;
} }
get searchFilters() { get searchFilters() {
return this._state.searchFilters; return this._state.searchFilters;
} }
set page(page: number) { set page(page: number) {
this._set({ page }); this.updateState({ page });
} }
set pageSize(pageSize: number) {
this._set({ pageSize }); set pageSize(pageSize: number) {
} this.updateState({ pageSize });
set searchTerm(searchTerm: string) { }
this._set({ searchTerm });
} set searchTerm(searchTerm: string) {
set searchFilters(searchFilters: {[key: string]: any}) { this.updateState({ searchTerm });
this._set({ searchFilters }); }
}
set sortColumn(sortColumn: SortColumn) {
this._set({ sortColumn });
}
set sortDirection(sortDirection: SortDirection) {
this._set({ sortDirection });
}
private _set(patch: Partial<State>) { set searchFilters(searchFilters: {[key: string]: any}) {
Object.assign(this._state, patch); this.updateState({ searchFilters: JSON.stringify(searchFilters) });
this._search(); }
}
set sortColumn(sortColumn: SortColumn) {
this.updateState({ sortColumn });
}
set sortDirection(sortDirection: SortDirection) {
this.updateState({ sortDirection });
}
private updateState(patch: any) {
this.router.navigate([], {
relativeTo: this.route,
queryParams: patch ,
queryParamsHandling: 'merge'
});
}
private _set(patch: Partial<State>) {
Object.assign(this._state, patch);
this._search();
}
} }

View File

@@ -44,6 +44,7 @@ export class RichtextTypeComponent extends FormlyFieldInput implements OnInit {
autoresize_bottom_margin: 0, autoresize_bottom_margin: 0,
body_class: "contract-body", body_class: "contract-body",
content_style: ".contract-body { font-family: 'Century Schoolbook', 'sans-serif' }", content_style: ".contract-body { font-family: 'Century Schoolbook', 'sans-serif' }",
entity_encoding: 'raw',
paste_preprocess: function (plugin: any, args: any) { paste_preprocess: function (plugin: any, args: any) {
console.log(args.content) console.log(args.content)
let container = document.createElement('div'); let container = document.createElement('div');
@@ -80,7 +81,7 @@ export class RichtextTypeComponent extends FormlyFieldInput implements OnInit {
} }
} }
getInitConfig() { getInitConfig(): any {
return {...this.init_common, ...( this.multiline ? this.init_multiline : this.init_singleline)}; return {...this.init_common, ...( this.multiline ? this.init_multiline : this.init_singleline)};
} }

View File

@@ -354,5 +354,69 @@
<target>Mettre à jour</target> <target>Mettre à jour</target>
</segment> </segment>
</unit> </unit>
<unit id="6061331044524123789">
<segment state="initial">
<source>Authentication required</source>
<target>Authentification nécessaire</target>
</segment>
</unit>
<unit id="3624268617519726175">
<segment state="initial">
<source>Permissions too low</source>
<target>Permissions trop basses</target>
</segment>
</unit>
<unit id="6833883791906187270">
<segment state="initial">
<source>Download Link:</source>
<target>Lien de téléchargement:</target>
</segment>
</unit>
<unit id="8168599357858858911">
<segment state="initial">
<source>Preview Link:</source>
<target>Lien de prévisualisation:</target>
</segment>
</unit>
<unit id="1295614462098694869">
<segment state="initial">
<source>Preview</source>
<target>Prévisualisation</target>
</segment>
</unit>
<unit id="2445188258613609179">
<segment state="initial">
<source>Signature</source>
<target>Signature</target>
</segment>
</unit>
<unit id="8474971383445371291">
<segment state="initial">
<source>
<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Cette page est à la destination exclusive de <pc id="1" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;"><ph id="2" equiv="INTERPOLATION" disp="{{ this.signatory }}"/></pc></pc>
<pc id="3" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Si vous n&apos;êtes <pc id="4" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">pas</pc> <ph id="5" equiv="INTERPOLATION" disp="{{ this.signatory }}"/>, veuillez <pc id="6" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">fermer cette page immédiatement</pc> et surpprimer tous les liens en votre possession menant vers celle-ci.</pc>
<pc id="7" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l&apos;article L.229 du code pénal de l&apos;Etat de San Andreas pour <pc id="8" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">usurpation d&apos;identité</pc> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu&apos;à des poursuites civiles.</pc>
<pc id="9" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Le cabinet Cooper, Hillman &amp; Toshi LLC</pc>
</source>
<target>
<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Cette page est à la destination exclusive de <pc id="1" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;"><ph id="2" equiv="INTERPOLATION" disp="{{ this.signatory }}"/></pc></pc>
<pc id="3" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Si vous n&apos;êtes <pc id="4" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">pas</pc> <ph id="5" equiv="INTERPOLATION" disp="{{ this.signatory }}"/>, veuillez <pc id="6" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">fermer cette page immédiatement</pc> et surpprimer tous les liens en votre possession menant vers celle-ci.</pc>
<pc id="7" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l&apos;article L.229 du code pénal de l&apos;Etat de San Andreas pour <pc id="8" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">usurpation d&apos;identité</pc> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu&apos;à des poursuites civiles.</pc>
<pc id="9" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Le cabinet Cooper, Hillman &amp; Toshi LLC</pc>
</target>
</segment>
</unit>
<unit id="7430416142942514215">
<segment state="initial">
<source>Publish</source>
<target>Publier</target>
</segment>
</unit>
<unit id="2990108023996257960">
<segment state="initial">
<source>This Contract has already been signed by</source>
<target>Ce contrat a déjà été signé par</target>
</segment>
</unit>
</file> </file>
</xliff> </xliff>

View File

@@ -292,5 +292,55 @@
<source>100 items per page</source> <source>100 items per page</source>
</segment> </segment>
</unit> </unit>
<unit id="3624268617519726175">
<segment>
<source>Permissions too low</source>
</segment>
</unit>
<unit id="6061331044524123789">
<segment>
<source>Authentication required</source>
</segment>
</unit>
<unit id="6833883791906187270">
<segment>
<source>Download Link:</source>
</segment>
</unit>
<unit id="8168599357858858911">
<segment>
<source>Preview Link:</source>
</segment>
</unit>
<unit id="1295614462098694869">
<segment>
<source>Preview</source>
</segment>
</unit>
<unit id="2445188258613609179">
<segment>
<source>Signature</source>
</segment>
</unit>
<unit id="7430416142942514215">
<segment>
<source>Publish</source>
</segment>
</unit>
<unit id="8474971383445371291">
<segment>
<source>
<pc id="0" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Cette page est à la destination exclusive de <pc id="1" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;"><ph id="2" equiv="INTERPOLATION" disp="{{ this.signatory }}"/></pc></pc>
<pc id="3" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Si vous n&apos;êtes <pc id="4" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">pas</pc> <ph id="5" equiv="INTERPOLATION" disp="{{ this.signatory }}"/>, veuillez <pc id="6" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">fermer cette page immédiatement</pc> et surpprimer tous les liens en votre possession menant vers celle-ci.</pc>
<pc id="7" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l&apos;article L.229 du code pénal de l&apos;Etat de San Andreas pour <pc id="8" equivStart="START_TAG_STRONG" equivEnd="CLOSE_TAG_STRONG" type="other" dispStart="&lt;strong&gt;" dispEnd="&lt;/strong&gt;">usurpation d&apos;identité</pc> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu&apos;à des poursuites civiles.</pc>
<pc id="9" equivStart="START_PARAGRAPH" equivEnd="CLOSE_PARAGRAPH" type="other" dispStart="&lt;p&gt;" dispEnd="&lt;/p&gt;">Le cabinet Cooper, Hillman &amp; Toshi LLC</pc>
</source>
</segment>
</unit>
<unit id="2990108023996257960">
<segment>
<source>This Contract has already been signed by</source>
</segment>
</unit>
</file> </file>
</xliff> </xliff>

View File

@@ -26,3 +26,14 @@
.contract-body { .contract-body {
font-family: 'Century Schoolbook'; font-family: 'Century Schoolbook';
} }
.nav-link {
color: #D2BA6F;
--bs-nav-link-hover-color: #9c8a51;
--bs-nav-pills-link-active-color: #D2BA6F;
--bs-nav-pills-link-active-bg: #114856;
}
.nav-link.active {
border: #D2BA6F solid 2px;
}