Customer notes feature finished

This commit is contained in:
Vins 2024-06-05 09:39:30 +02:00
parent f32862bbb7
commit 153be65d4c
17 changed files with 568 additions and 42 deletions

84
package-lock.json generated
View File

@ -24,7 +24,7 @@
"eslint-config-next": "13.2.4", "eslint-config-next": "13.2.4",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.139", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151",
"next": "^14.2.3", "next": "^14.2.3",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"react": "18.2.0", "react": "18.2.0",
@ -331,9 +331,9 @@
} }
}, },
"node_modules/@eslint-community/regexpp": { "node_modules/@eslint-community/regexpp": {
"version": "4.10.0", "version": "4.10.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==",
"engines": { "engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0" "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
} }
@ -1250,15 +1250,18 @@
} }
}, },
"node_modules/array.prototype.tosorted": { "node_modules/array.prototype.tosorted": {
"version": "1.1.3", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
"integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
"dependencies": { "dependencies": {
"call-bind": "^1.0.5", "call-bind": "^1.0.7",
"define-properties": "^1.2.1", "define-properties": "^1.2.1",
"es-abstract": "^1.22.3", "es-abstract": "^1.23.3",
"es-errors": "^1.1.0", "es-errors": "^1.3.0",
"es-shim-unscopables": "^1.0.2" "es-shim-unscopables": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
} }
}, },
"node_modules/arraybuffer.prototype.slice": { "node_modules/arraybuffer.prototype.slice": {
@ -1353,14 +1356,14 @@
"optional": true "optional": true
}, },
"node_modules/bare-fs": { "node_modules/bare-fs": {
"version": "2.3.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.0.tgz", "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz",
"integrity": "sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==", "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"bare-events": "^2.0.0", "bare-events": "^2.0.0",
"bare-path": "^2.0.0", "bare-path": "^2.0.0",
"bare-stream": "^1.0.0" "bare-stream": "^2.0.0"
} }
}, },
"node_modules/bare-os": { "node_modules/bare-os": {
@ -1379,12 +1382,12 @@
} }
}, },
"node_modules/bare-stream": { "node_modules/bare-stream": {
"version": "1.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.0.1.tgz",
"integrity": "sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==", "integrity": "sha512-ubLyoDqPnUf5o0kSFp709HC0WRZuxVuh4pbte5eY95Xvx5bdvz07c2JFmXBfqqe60q+9PJ8S4X5GRvmcNSKMxg==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"streamx": "^2.16.1" "streamx": "^2.18.0"
} }
}, },
"node_modules/base64-js": { "node_modules/base64-js": {
@ -1508,9 +1511,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001625", "version": "1.0.30001628",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001628.tgz",
"integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==", "integrity": "sha512-S3BnR4Kh26TBxbi5t5kpbcUlLJb9lhtDXISDPwOfI+JoC+ik0QksvkZtUVyikw3hjnkgkMPSJ8oIM9yMm9vflA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -1772,9 +1775,9 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@ -1926,9 +1929,9 @@
} }
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.16.1", "version": "5.17.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz",
"integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==",
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
"tapable": "^2.2.0" "tapable": "^2.2.0"
@ -3489,7 +3492,7 @@
} }
}, },
"node_modules/le-coffre-resources": { "node_modules/le-coffre-resources": {
"resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#f45dc562f3eabfa91e11d56b870e2e053c486135", "resolved": "git+ssh://git@github.com/smart-chain-fr/leCoffre-resources.git#67348c776c8319ed8707ade406a2ce454e7adc2e",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
@ -4674,12 +4677,13 @@
} }
}, },
"node_modules/streamx": { "node_modules/streamx": {
"version": "2.17.0", "version": "2.18.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.17.0.tgz", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz",
"integrity": "sha512-mzRXEeafEA0skX5XLiDht/zdIqEVs4kgayUTFHDoMjiaZ2kC7DoFsQDJVXRILI2Qme/kWXxLpuU6P0B+xcXpFA==", "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==",
"dependencies": { "dependencies": {
"fast-fifo": "^1.1.0", "fast-fifo": "^1.3.2",
"queue-tick": "^1.0.1" "queue-tick": "^1.0.1",
"text-decoder": "^1.1.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"bare-events": "^2.2.0" "bare-events": "^2.2.0"
@ -4874,6 +4878,14 @@
"streamx": "^2.15.0" "streamx": "^2.15.0"
} }
}, },
"node_modules/text-decoder": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz",
"integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/text-table": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -4915,9 +4927,9 @@
} }
}, },
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.6.2", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
}, },
"node_modules/tsutils": { "node_modules/tsutils": {
"version": "3.21.0", "version": "3.21.0",

View File

@ -26,7 +26,7 @@
"eslint-config-next": "13.2.4", "eslint-config-next": "13.2.4",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.139", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151",
"next": "^14.2.3", "next": "^14.2.3",
"prettier": "^2.8.7", "prettier": "^2.8.7",
"react": "18.2.0", "react": "18.2.0",

View File

@ -0,0 +1,69 @@
import BaseCustomer from "../BaseCustomer";
import Note from "le-coffre-resources/dist/Customer/Note";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetFoldersParams {
q?: {
select?: {};
where?: {};
orderBy?: {}[];
include?: {};
};
}
export default class Notes extends BaseCustomer {
private static instance: Notes;
private readonly baseURl = this.namespaceUrl.concat("/notes");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
/**
* @description : Get a note by uid
*/
public async getByUid(uid: string, q?: any): Promise<Note> {
const url = new URL(this.baseURl.concat(`/${uid}`));
if (q) Object.entries(q).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<Note>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Create a note
*/
public async post(note: Partial<Note>): Promise<Note> {
const url = new URL(this.baseURl);
try {
return await this.postRequest(url, note);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
/**
* @description : Update the note
*/
public async put(uid: string, body: Partial<Note>): Promise<Note> {
const url = new URL(this.baseURl.concat(`/${uid}`));
try {
return await this.putRequest(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -9,18 +9,22 @@ import React from "react";
import Typography, { ITypo } from "../Typography"; import Typography, { ITypo } from "../Typography";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { AnchorStatus } from "@Front/Components/Layouts/Folder/FolderInformation"; import { AnchorStatus } from "@Front/Components/Layouts/Folder/FolderInformation";
import Note from "le-coffre-resources/dist/Customer/Note";
type IProps = { type IProps = {
folder: OfficeFolder; folder: OfficeFolder;
type: EFolderBoxInformationType; type: EFolderBoxInformationType;
isArchived?: boolean; isArchived?: boolean;
anchorStatus: AnchorStatus; anchorStatus: AnchorStatus;
customerUid?: string;
note?: Note | null;
}; };
export enum EFolderBoxInformationType { export enum EFolderBoxInformationType {
INFORMATIONS = "informations", INFORMATIONS = "informations",
DESCRIPTION = "description", DESCRIPTION = "description",
ARCHIVED_DESCRIPTION = "archivedDescription", ARCHIVED_DESCRIPTION = "archivedDescription",
NOTE = "note",
} }
export default function FolderBoxInformation(props: IProps) { export default function FolderBoxInformation(props: IProps) {
@ -32,10 +36,27 @@ export default function FolderBoxInformation(props: IProps) {
.get() .get()
.modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", props.folder.uid ?? ""); .modules.pages.Folder.pages.EditInformations.props.path.replace("[folderUid]", props.folder.uid ?? "");
const path = type === EFolderBoxInformationType.DESCRIPTION ? editDescriptionPath : editInformationsPath; let createOrUpdateNotePath = Module.getInstance()
.get()
.modules.pages.Notes.pages.EditNote.props.path.replace("[noteUid]", props.note?.uid ?? "");
if (!props.note) {
createOrUpdateNotePath = Module.getInstance()
.get()
.modules.pages.Notes.pages.CreateNote.props.path.replace("[folderUid]", props.folder.uid ?? "");
createOrUpdateNotePath = createOrUpdateNotePath.replace("[customerUid]", props.customerUid ?? "");
}
let path = editInformationsPath;
if (type === EFolderBoxInformationType.DESCRIPTION) {
path = editDescriptionPath;
} else if (type === EFolderBoxInformationType.NOTE) {
path = createOrUpdateNotePath;
}
return ( return (
<div className={classNames(classes["root"], type !== EFolderBoxInformationType.INFORMATIONS && classes["single-information"])}> <div className={classNames(classes["root"], type !== EFolderBoxInformationType.INFORMATIONS && classes["single-information"])}>
<div className={classes["content"]}>{renderContentByType(props.folder, type)}</div> <div className={classes["content"]}>{renderContentByType(props.folder, type, props.note)}</div>
{!isArchived && props.anchorStatus === AnchorStatus.NOT_ANCHORED && ( {!isArchived && props.anchorStatus === AnchorStatus.NOT_ANCHORED && (
<Link href={path} className={classes["edit-icon-container"]}> <Link href={path} className={classes["edit-icon-container"]}>
<Image src={PenICon} alt="edit informations" /> <Image src={PenICon} alt="edit informations" />
@ -44,7 +65,7 @@ export default function FolderBoxInformation(props: IProps) {
</div> </div>
); );
function renderContentByType(folder: OfficeFolder, type: EFolderBoxInformationType) { function renderContentByType(folder: OfficeFolder, type: EFolderBoxInformationType, note?: Note | null) {
switch (type) { switch (type) {
case EFolderBoxInformationType.DESCRIPTION: case EFolderBoxInformationType.DESCRIPTION:
return ( return (
@ -53,6 +74,13 @@ export default function FolderBoxInformation(props: IProps) {
<Typography typo={ITypo.P_18}>{folder.description ?? ""}</Typography> <Typography typo={ITypo.P_18}>{folder.description ?? ""}</Typography>
</div> </div>
); );
case EFolderBoxInformationType.NOTE:
return (
<div className={classes["text-container"]}>
<Typography typo={ITypo.NAV_INPUT_16}>Note client</Typography>
<Typography typo={ITypo.P_18}>{note?.content ?? ""}</Typography>
</div>
);
case EFolderBoxInformationType.ARCHIVED_DESCRIPTION: case EFolderBoxInformationType.ARCHIVED_DESCRIPTION:
return ( return (
<div className={classes["text-container"]}> <div className={classes["text-container"]}>

View File

@ -79,5 +79,10 @@
} }
} }
} }
.notes {
margin-top: 15px;
width: 50%;
}
} }
} }

View File

@ -17,6 +17,7 @@ import classes from "./classes.module.scss";
import DocumentList from "./DocumentList"; import DocumentList from "./DocumentList";
import UserFolderHeader from "./UserFolderHeader"; import UserFolderHeader from "./UserFolderHeader";
import { AnchorStatus } from "@Front/Components/Layouts/Folder/FolderInformation"; import { AnchorStatus } from "@Front/Components/Layouts/Folder/FolderInformation";
import FolderBoxInformation, { EFolderBoxInformationType } from "../FolderBoxInformation";
type IProps = { type IProps = {
customer: Customer; customer: Customer;
@ -73,6 +74,9 @@ export default class UserFolder extends React.Component<IProps, IState> {
documentAskedSubtitle = "Aucun document en attente"; documentAskedSubtitle = "Aucun document en attente";
} }
} }
//get the note of the folder that has customer_uid = this.props.customer.uid
const folderNote = this.props.folder.notes?.find((note) => note.customer?.uid === this.props.customer.uid);
return ( return (
<div className={classes["root"]} data-opened={this.props.isOpened.toString()}> <div className={classes["root"]} data-opened={this.props.isOpened.toString()}>
<Confirm <Confirm
@ -141,6 +145,15 @@ export default class UserFolder extends React.Component<IProps, IState> {
</div> </div>
)} )}
</div> </div>
<div className={classes["content"]}>
<FolderBoxInformation
anchorStatus={AnchorStatus.NOT_ANCHORED}
folder={this.props.folder}
type={EFolderBoxInformationType.NOTE}
note={folderNote}
customerUid={this.props.customer.uid}
/>
</div>
</div> </div>
)} )}
</div> </div>

View File

@ -59,6 +59,16 @@
background-color: gray; background-color: gray;
margin: 0 20px; /* Adjust the margin as needed */ margin: 0 20px; /* Adjust the margin as needed */
} }
.note-box {
border: 1px solid #e0e0e0; /* Light grey border */
margin-top: 25px;
padding: 10px;
width: 100%;
height: 100px; /* Adjust height as needed */
box-sizing: border-box;
position: relative;
}
} }
.sub-container { .sub-container {

View File

@ -4,7 +4,7 @@ import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import DepositDocument from "@Front/Components/DesignSystem/DepositDocument"; import DepositDocument from "@Front/Components/DesignSystem/DepositDocument";
import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography"; import Typography, { ITypo, ITypoColor } from "@Front/Components/DesignSystem/Typography";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate"; import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Customer, { Document, DocumentType, OfficeFolder } from "le-coffre-resources/dist/Customer"; import Customer, { Document, DocumentType, Note, OfficeFolder } from "le-coffre-resources/dist/Customer";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
@ -24,6 +24,7 @@ export default function ClientDashboard(props: IProps) {
const [customer, setCustomer] = useState<Customer | null>(null); const [customer, setCustomer] = useState<Customer | null>(null);
const [contact, setContact] = useState<Customer["contact"] | null>(null); const [contact, setContact] = useState<Customer["contact"] | null>(null);
const [folder, setFolder] = useState<OfficeFolder | null>(null); const [folder, setFolder] = useState<OfficeFolder | null>(null);
const [note, setNote] = useState<Note | null>(null);
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false); const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
const getDocuments = useCallback(async () => { const getDocuments = useCallback(async () => {
@ -36,6 +37,11 @@ export default function ClientDashboard(props: IProps) {
q: { q: {
office: true, office: true,
customers: true, customers: true,
notes: {
include: {
customer: true,
},
},
stakeholders: { stakeholders: {
include: { include: {
contact: true, contact: true,
@ -44,7 +50,6 @@ export default function ClientDashboard(props: IProps) {
}, },
}, },
}); });
//Loop through the folder stakeholders, if there is at least one stakeholder that role is "Collaborateur" set contact to this stakeholders.contact, else, take the first stakeholders of the list //Loop through the folder stakeholders, if there is at least one stakeholder that role is "Collaborateur" set contact to this stakeholders.contact, else, take the first stakeholders of the list
const contact = folder.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact; const contact = folder.stakeholders!.find((stakeholder) => stakeholder.office_role?.name === "Collaborateur")?.contact;
setContact(contact ?? folder.stakeholders![0]!.contact); setContact(contact ?? folder.stakeholders![0]!.contact);
@ -52,6 +57,9 @@ export default function ClientDashboard(props: IProps) {
const actualCustomer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email); const actualCustomer = folder?.customers?.find((customer) => customer.contact?.email === jwt?.email);
if (!actualCustomer) throw new Error("Customer not found"); if (!actualCustomer) throw new Error("Customer not found");
const note = folder.notes?.find((note) => note.customer?.uid === actualCustomer.uid);
if (!note) throw new Error("Note not found");
const query: IGetDocumentsparams = { const query: IGetDocumentsparams = {
where: { depositor: { uid: actualCustomer.uid }, folder_uid: folderUid as string }, where: { depositor: { uid: actualCustomer.uid }, folder_uid: folderUid as string },
include: { include: {
@ -78,6 +86,7 @@ export default function ClientDashboard(props: IProps) {
setFolder(folder); setFolder(folder);
setDocuments(documentList); setDocuments(documentList);
setCustomer(actualCustomer); setCustomer(actualCustomer);
setNote(note);
}, [folderUid]); }, [folderUid]);
const onCloseModalAddDocument = useCallback(() => { const onCloseModalAddDocument = useCallback(() => {
@ -136,7 +145,13 @@ export default function ClientDashboard(props: IProps) {
sélectionnez le document correspondant. <br /> En déposant un document, celui-ci est automatiquement enregistré et sélectionnez le document correspondant. <br /> En déposant un document, celui-ci est automatiquement enregistré et
transmis à votre notaire. transmis à votre notaire.
</Typography> </Typography>
<div className={classes["note-box"]}>
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
{note?.content}
</Typography>
</div> </div>
</div>
<div className={classes["contact"]}> <div className={classes["contact"]}>
<Typography typo={ITypo.P_SB_18} className={classes["contact-text"]} color={ITypoColor.GREY}> <Typography typo={ITypo.P_SB_18} className={classes["contact-text"]} color={ITypoColor.GREY}>
<p> <p>

View File

@ -0,0 +1,56 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
min-height: 100%;
align-items: flex-start;
width: fit-content;
.back-arrow {
margin-bottom: 24px;
}
.form {
width: 100%;
.content {
margin-top: 32px;
>:not(:last-child) {
margin-bottom: 24px;
}
}
.button-container {
width: 100%;
display: flex;
text-align: center;
margin-top: 24px;
.cancel-button {
display: flex;
margin-right: 32px;
}
@media (max-width: $screen-m) {
display: flex;
flex-direction: column-reverse;
.cancel-button {
margin-left: 0;
margin-top: 12px;
>* {
flex: 1;
}
}
>* {
width: 100%;
}
}
}
}
}

View File

@ -0,0 +1,114 @@
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module";
import { OfficeFolder } from "le-coffre-resources/dist/Customer";
import Link from "next/link";
import { NextRouter, useRouter } from "next/router";
import BasePage from "../../Base";
import classes from "./classes.module.scss";
import Customer from "le-coffre-resources/dist/Customer";
import Note from "le-coffre-resources/dist/Customer/Note";
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
import Notes from "@Front/Api/LeCoffreApi/Customer/Notes/Notes";
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
type IProps = {};
type IPropsClass = IProps & {
folderUid: string;
customerUid: string;
router: NextRouter;
};
type IState = {
folder: OfficeFolder | null;
customer: Customer | null;
note: Note | null;
};
class CreateCustomerNoteClass extends BasePage<IPropsClass, IState> {
private backwardPath = Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", this.props.folderUid!);
constructor(props: IPropsClass) {
super(props);
this.state = {
folder: null,
customer: null,
note: null,
};
this.onFormSubmit = this.onFormSubmit.bind(this);
}
public override render(): JSX.Element {
return (
<DefaultNotaryDashboard title={"Modifier la note client"}>
<div className={classes["root"]}>
<div className={classes["back-arrow"]}>
<BackArrow url={this.backwardPath} />
</div>
<Typography typo={ITypo.H1Bis}>Modifier la note du client</Typography>
<Form className={classes["form"]} onSubmit={this.onFormSubmit}>
<div className={classes["content"]}>
<TextAreaField
name="content"
placeholder="Note à l'attention du client"
defaultValue={this.state.note?.content ?? ""}
/>
</div>
<div className={classes["button-container"]}>
<Link href={this.backwardPath} className={classes["cancel-button"]}>
<Button variant={EButtonVariant.GHOST}>Annuler</Button>
</Link>
<Button type="submit">Enregistrer</Button>
</div>
</Form>
</div>
</DefaultNotaryDashboard>
);
}
public override async componentDidMount() {
// const note = await Notes.getInstance().getByUid(this.props.noteUid, query);
const folder = await Folders.getInstance().getByUid(this.props.folderUid, { note: true });
const customer = await Customers.getInstance().getByUid(this.props.customerUid);
//get the note of the folder that has customer_uid = this.props.customer.uid
// const folderNote = folder.notes?.find((note) => note.customer?.uid === this.props.customerUid);
// this.setState({ note, folder: note.office_folder || null, customer: note.customer || null });
this.setState({ note: null, folder: folder, customer: customer });
}
private async onFormSubmit(e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) {
try {
if (!this.state.folder || !this.state.customer) {
throw new Error("Folder or customer not found");
}
const note = {
content: values["content"],
office_folder: this.state.folder,
customer: this.state.customer,
};
await Notes.getInstance().post(note);
this.props.router.push(this.backwardPath);
} catch (error) {
console.error(error);
}
}
}
export default function UpdateCustomerNote(props: IProps) {
const router = useRouter();
let { folderUid, customerUid } = router.query;
folderUid = folderUid as string;
customerUid = customerUid as string;
return <CreateCustomerNoteClass folderUid={folderUid} customerUid={customerUid} router={router} />;
}

View File

@ -489,6 +489,11 @@ export default function FolderInformation(props: IProps) {
}, },
}, },
folder_anchor: true, folder_anchor: true,
notes: {
include: {
customer: true,
},
},
}, },
}; };

View File

@ -0,0 +1,56 @@
@import "@Themes/constants.scss";
.root {
display: flex;
flex-direction: column;
min-height: 100%;
align-items: flex-start;
width: fit-content;
.back-arrow {
margin-bottom: 24px;
}
.form {
width: 100%;
.content {
margin-top: 32px;
>:not(:last-child) {
margin-bottom: 24px;
}
}
.button-container {
width: 100%;
display: flex;
text-align: center;
margin-top: 24px;
.cancel-button {
display: flex;
margin-right: 32px;
}
@media (max-width: $screen-m) {
display: flex;
flex-direction: column-reverse;
.cancel-button {
margin-left: 0;
margin-top: 12px;
>* {
flex: 1;
}
}
>* {
width: 100%;
}
}
}
}
}

View File

@ -0,0 +1,109 @@
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Form from "@Front/Components/DesignSystem/Form";
import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField";
import Typography, { ITypo } from "@Front/Components/DesignSystem/Typography";
import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard";
import Module from "@Front/Config/Module";
import Link from "next/link";
import { NextRouter, useRouter } from "next/router";
import BasePage from "../../Base";
import classes from "./classes.module.scss";
import Note from "le-coffre-resources/dist/Customer/Note";
import Notes from "@Front/Api/LeCoffreApi/Customer/Notes/Notes";
type IProps = {};
type IPropsClass = IProps & {
noteUid: string;
router: NextRouter;
};
type IState = {
note: Note | null;
backwardPath: string;
};
class UpdateCustomerNoteClass extends BasePage<IPropsClass, IState> {
constructor(props: IPropsClass) {
super(props);
this.state = {
note: null,
backwardPath: "",
};
this.onFormSubmit = this.onFormSubmit.bind(this);
}
public override render(): JSX.Element {
return (
<DefaultNotaryDashboard title={"Modifier la note client"}>
<div className={classes["root"]}>
<div className={classes["back-arrow"]}>
<BackArrow url={this.state.backwardPath} />
</div>
<Typography typo={ITypo.H1Bis}>Modifier la note du client</Typography>
<Form className={classes["form"]} onSubmit={this.onFormSubmit}>
<div className={classes["content"]}>
<TextAreaField
name="content"
placeholder="Note à l'attention du client"
defaultValue={this.state.note?.content ?? ""}
/>
</div>
<div className={classes["button-container"]}>
<Link href={this.state.backwardPath} className={classes["cancel-button"]}>
<Button variant={EButtonVariant.GHOST}>Annuler</Button>
</Link>
<Button type="submit">Enregistrer</Button>
</div>
</Form>
</div>
</DefaultNotaryDashboard>
);
}
public override async componentDidMount() {
const query = {
q: {
customer: "true",
folder: "true",
},
};
const note = await Notes.getInstance().getByUid(this.props.noteUid, query);
// const folder = await Folders.getInstance().getByUid(this.props.folderUid, { note: true });
//get the note of the folder that has customer_uid = this.props.customer.uid
// const folderNote = folder.notes?.find((note) => note.customer?.uid === this.props.customerUid);
this.setState({
note,
backwardPath: Module.getInstance()
.get()
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", note.folder?.uid!),
});
}
private async onFormSubmit(e: React.FormEvent<HTMLFormElement> | null, values: { [key: string]: string }) {
try {
const note = {
content: values["content"],
};
await Notes.getInstance().put(this.props.noteUid, note);
this.props.router.push(this.state.backwardPath);
// await Folders.getInstance().put(this.props.folderUid, values);
// this.props.router.push(this.backwardPath);
} catch (error) {
console.error(error);
}
}
}
export default function UpdateCustomerNote(props: IProps) {
const router = useRouter();
let { noteUid } = router.query;
noteUid = noteUid as string;
return <UpdateCustomerNoteClass noteUid={noteUid} router={router} />;
}

View File

@ -215,6 +215,7 @@ class FolderInformationClass extends BasePage<IPropsClass, IState> {
deed: { include: { deed_type: "true" } }, deed: { include: { deed_type: "true" } },
office: "true", office: "true",
customers: { include: { contact: true } }, customers: { include: { contact: true } },
notes: "true",
}, },
}; };
const folder = await Folders.getInstance().getByUid(this.props.selectedFolderUid, query); const folder = await Folders.getInstance().getByUid(this.props.selectedFolderUid, query);

View File

@ -360,6 +360,29 @@
} }
} }
} }
},
"Notes": {
"enabled": true,
"props": {
"path": "/notes",
"labelKey": "notes"
},
"pages": {
"EditNote": {
"enabled": true,
"props": {
"path": "/notes/[noteUid]/edit",
"labelKey": "edit_note"
}
},
"CreateNote": {
"enabled": true,
"props": {
"path": "/notes/folder/[folderUid]/customer/[customerUid]/create",
"labelKey": "create_note"
}
}
}
} }
} }
} }

View File

@ -0,0 +1,5 @@
import UpdateCustomerNote from "@Front/Components/Layouts/Folder/UpdateCustomerNote";
export default function Route() {
return <UpdateCustomerNote />;
}

View File

@ -0,0 +1,5 @@
import CreateCustomerNote from "@Front/Components/Layouts/Folder/CreateCustomerNote";
export default function Route() {
return <CreateCustomerNote />;
}