From 608b9583cebb745c54a7dc2e31e988d7b39b1271 Mon Sep 17 00:00:00 2001 From: Maxime Sallerin <97036207+maxime-sallerin@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:58:13 +0200 Subject: [PATCH] Refacto/folder information wip (#172) --- .../EmptyAlert/classes.module.scss | 20 + .../DesignSystem/EmptyAlert/index.tsx | 28 + .../DesignSystem/Modal/classes.module.scss | 2 +- src/front/Components/Elements/Tabs/index.tsx | 2 +- .../ClientView/ClientBox/classes.module.scss | 11 + .../ClientView/ClientBox/index.tsx | 57 ++ .../DocumentTables/classes.module.scss | 14 + .../ClientView/DocumentTables/index.tsx | 155 +++++ .../ClientView/NoDocument/classes.module.scss | 7 + .../ClientView/NoDocument/index.tsx | 20 + .../ClientView/classes.module.scss | 26 + .../FolderInformation/ClientView/index.tsx | 92 +++ .../ClientSection/classes.module.scss | 0 .../ClientSection/index.tsx | 0 .../FolderInformationOld/classes.module.scss | 113 ++++ .../FolderInformationOld/index.tsx | 541 ++++++++++++++++++ .../InformationSection/classes.module.scss | 39 ++ .../InformationSection/index.tsx | 71 +++ .../NoClientView/AddClientSection/index.tsx | 34 ++ .../NoClientView/DeleteFolderModal/index.tsx | 42 ++ .../NoClientView/classes.module.scss | 11 + .../FolderInformation/NoClientView/index.tsx | 41 ++ .../FolderInformation/classes.module.scss | 88 +-- .../Folder/FolderInformation/index.tsx | 533 ++--------------- 24 files changed, 1379 insertions(+), 568 deletions(-) create mode 100644 src/front/Components/DesignSystem/EmptyAlert/classes.module.scss create mode 100644 src/front/Components/DesignSystem/EmptyAlert/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/ClientView/index.tsx rename src/front/Components/Layouts/Folder/FolderInformation/{ => FolderInformationOld}/ClientSection/classes.module.scss (100%) rename src/front/Components/Layouts/Folder/FolderInformation/{ => FolderInformationOld}/ClientSection/index.tsx (100%) create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/InformationSection/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/NoClientView/AddClientSection/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/NoClientView/DeleteFolderModal/index.tsx create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/NoClientView/classes.module.scss create mode 100644 src/front/Components/Layouts/Folder/FolderInformation/NoClientView/index.tsx diff --git a/src/front/Components/DesignSystem/EmptyAlert/classes.module.scss b/src/front/Components/DesignSystem/EmptyAlert/classes.module.scss new file mode 100644 index 00000000..d356ef14 --- /dev/null +++ b/src/front/Components/DesignSystem/EmptyAlert/classes.module.scss @@ -0,0 +1,20 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: var(--spacing-3, 24px); + gap: var(--spacing-lg, 24px); + + border-radius: var(--radius-minimal, 8px); + background: var(--primary-weak-higlight, #e5eefa); + + text-align: center; + + svg { + width: 32px; + stroke: var(--primary-weak-contrast); + } +} diff --git a/src/front/Components/DesignSystem/EmptyAlert/index.tsx b/src/front/Components/DesignSystem/EmptyAlert/index.tsx new file mode 100644 index 00000000..878db597 --- /dev/null +++ b/src/front/Components/DesignSystem/EmptyAlert/index.tsx @@ -0,0 +1,28 @@ +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import React from "react"; + +import classes from "./classes.module.scss"; + +type IProps = { + icon: React.ReactNode; + title: string; + description: string; + footer?: React.ReactNode; +}; + +export default function EmptyAlert(props: IProps) { + const { icon, title, description, footer } = props; + + return ( +
+ {icon} + + {title} + + + {description} + + {footer} +
+ ); +} diff --git a/src/front/Components/DesignSystem/Modal/classes.module.scss b/src/front/Components/DesignSystem/Modal/classes.module.scss index da05f014..f674201d 100644 --- a/src/front/Components/DesignSystem/Modal/classes.module.scss +++ b/src/front/Components/DesignSystem/Modal/classes.module.scss @@ -21,7 +21,7 @@ .header { display: flex; - align-items: center; + align-items: flex-start; justify-content: space-between; padding: var(--spacing-md, 16px) var(--modal-spacing, 16px); gap: var(--spacing-md, 16px); diff --git a/src/front/Components/Elements/Tabs/index.tsx b/src/front/Components/Elements/Tabs/index.tsx index 5d7eaae8..7906b750 100644 --- a/src/front/Components/Elements/Tabs/index.tsx +++ b/src/front/Components/Elements/Tabs/index.tsx @@ -8,7 +8,7 @@ import useOpenable from "@Front/Hooks/useOpenable"; export type ITabValue = T & { id: unknown; -}; +} type ITabInternal = ITab & { key?: string; diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/classes.module.scss new file mode 100644 index 00000000..55503cfe --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/classes.module.scss @@ -0,0 +1,11 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + width: fit-content; + padding: var(--spacing-md, 16px); + flex-direction: column; + gap: var(--spacing-md, 16px); + background: var(--primary-weak-higlight, #e5eefa); + min-width: 300px; +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/index.tsx new file mode 100644 index 00000000..891c64cd --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/ClientBox/index.tsx @@ -0,0 +1,57 @@ +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import { ICustomer } from ".."; +import classes from "./classes.module.scss"; +import { PencilSquareIcon, TrashIcon } from "@heroicons/react/24/outline"; +import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton"; +import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; + +type IProps = { + customer: ICustomer; +}; + +export default function ClientBox(props: IProps) { + const { customer } = props; + + return ( +
+
+ + {customer.contact?.last_name} + + } /> +
+
+ + Numéro de téléphone + + + {customer.contact?.phone_number} + +
+
+ + E-mail + + + {customer.contact?.email} + +
+
+ + Note client + + + TODO + +
+ + +
+ ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/classes.module.scss new file mode 100644 index 00000000..1e85e972 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/classes.module.scss @@ -0,0 +1,14 @@ +@import "@Themes/constants.scss"; + +.root { + width: 100%; + display: flex; + flex-direction: column; + gap: var(--spacing-xl, 32px); + + .title { + display: flex; + justify-content: space-between; + align-items: center; + } +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/index.tsx new file mode 100644 index 00000000..73f090b5 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/DocumentTables/index.tsx @@ -0,0 +1,155 @@ +import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents"; +import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; +import IconButton from "@Front/Components/DesignSystem/IconButton"; +import Table from "@Front/Components/DesignSystem/Table"; +import { IHead, IRowProps } from "@Front/Components/DesignSystem/Table/MuiTable"; +import Tag, { ETagColor, ETagVariant } from "@Front/Components/DesignSystem/Tag"; +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { Document } from "le-coffre-resources/dist/Customer"; +import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import classes from "./classes.module.scss"; + +type IProps = { + documents: Document[]; + totalOfDocumentTypes: number; +}; + +const header: readonly IHead[] = [ + { + key: "document_type", + title: "Type de document", + }, + { + key: "document_status", + title: "Statut", + }, + { + key: "created_at", + title: "Demandé le", + }, + { + key: "actions", + title: "Actions", + }, +]; + +export default function DocumentTables(props: IProps) { + const { documents: documentsProps, totalOfDocumentTypes } = props; + const [documents, setDocuments] = useState(documentsProps); + + useEffect(() => { + setDocuments(documentsProps); + }, [documentsProps]); + + const deleteAskedDocument = useCallback( + (uid: string | undefined) => { + if (!uid) return; + return Documents.getInstance() + .delete(uid) + .then(() => setDocuments(documents.filter((document) => document.uid !== uid))) + .catch((error) => console.warn(error)); + }, + [documents], + ); + + const askDocuments: IRowProps[] = useMemo( + () => + documents + .map((document) => { + if (document.document_status !== EDocumentStatus.ASKED) return null; + return { + key: document.uid, + document_type: document.document_type?.name ?? "_", + document_status: ( + + ), + created_at: document.created_at ? new Date(document.created_at).toLocaleDateString() : "_", + actions: deleteAskedDocument(document.uid)} />} />, + }; + }) + .filter((document) => document !== null) as IRowProps[], + [deleteAskedDocument, documents], + ); + + const toValidateDocuments: IRowProps[] = useMemo( + () => + documents + .map((document) => { + if (document.document_status !== EDocumentStatus.DEPOSITED) return null; + return { + key: document.uid, + document_type: document.document_type?.name ?? "_", + document_status: ( + + ), + created_at: document.created_at, + actions: } />, + }; + }) + .filter((document) => document !== null) as IRowProps[], + [documents], + ); + + const validatedDocuments: IRowProps[] = useMemo( + () => + documents + .map((document) => { + if (document.document_status !== EDocumentStatus.VALIDATED) return null; + return { + key: document.uid, + document_type: document.document_type?.name ?? "_", + document_status: ( + + ), + created_at: document.created_at, + actions: ( +
+ } /> + } /> +
+ ), + }; + }) + .filter((document) => document !== null) as IRowProps[], + [documents], + ); + + const refusedDocuments: IRowProps[] = useMemo( + () => + documents + .map((document) => { + if (document.document_status !== EDocumentStatus.REFUSED) return null; + return { + key: document.uid, + document_type: document.document_type?.name ?? "_", + document_status: ( + + ), + created_at: document.created_at, + actions: "", + }; + }) + .filter((document) => document !== null) as IRowProps[], + [documents], + ); + + const progressValidated = useMemo(() => validatedDocuments.length / totalOfDocumentTypes, [validatedDocuments, totalOfDocumentTypes]); + + return ( +
+
+ + Documents + + +
+ + {toValidateDocuments.length > 0 &&
} + {validatedDocuments.length > 0 &&
} + {refusedDocuments.length > 0 &&
} + + ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/classes.module.scss new file mode 100644 index 00000000..0594b1b7 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/classes.module.scss @@ -0,0 +1,7 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + flex-direction: column; + gap: var(--spacing-xl, 32px); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/index.tsx new file mode 100644 index 00000000..49de66dc --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/NoDocument/index.tsx @@ -0,0 +1,20 @@ +import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert"; +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import { DocumentIcon } from "@heroicons/react/24/outline"; + +import classes from "./classes.module.scss"; + +export default function NoDocument() { + return ( +
+ + Documents + + } + title="Aucune demande de document" + description="Vous n'avez encore demandé aucun document pour ce client. Pour commencer, cliquez sur le bouton ci-dessous pour créer une nouvelle demande de document." + /> +
+ ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/classes.module.scss new file mode 100644 index 00000000..b55462e7 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/classes.module.scss @@ -0,0 +1,26 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + flex-direction: column; + gap: var(--spacing-xl, 32px); + + .tab-container { + display: flex; + gap: var(--spacing-md, 16px); + justify-content: space-between; + align-items: center; + } + + .content { + display: flex; + gap: var(--spacing-lg, 24px); + + .client-box { + display: flex; + flex-direction: column; + + gap: var(--spacing-lg, 24px); + } + } +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientView/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/index.tsx new file mode 100644 index 00000000..71119c08 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/ClientView/index.tsx @@ -0,0 +1,92 @@ +import Tabs from "@Front/Components/Elements/Tabs"; +import Customer from "le-coffre-resources/dist/Customer"; +import { OfficeFolder } from "le-coffre-resources/dist/Notary"; + +import { useMemo, useState } from "react"; + +import { AnchorStatus } from ".."; +import classes from "./classes.module.scss"; +import ClientBox from "./ClientBox"; +import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; +import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline"; +import Module from "@Front/Config/Module"; +import Link from "next/link"; +import NoDocument from "./NoDocument"; +import DocumentTables from "./DocumentTables"; + +type IProps = { + folder: OfficeFolder; + anchorStatus: AnchorStatus; +}; + +export type ICustomer = Customer & { id: string }; + +export default function ClientView(props: IProps) { + const { folder, anchorStatus } = props; + + const customers: ICustomer[] = useMemo( + () => + folder?.customers?.map((customer) => ({ + id: customer.uid ?? "", + ...customer, + })) ?? [], + [folder], + ); + + const [customer, setCustomer] = useState<(typeof customers)[number]>(customers[0]!); + + const tabs = useMemo( + () => + customers.map((customer) => ({ + label: `${customer.contact?.first_name} ${customer.contact?.last_name}`, + key: customer.uid, + value: customer, + })), + [customers], + ); + + const doesCustomerHaveDocument = useMemo(() => customer.documents && customer.documents.length > 0, [customer]); + + const totalOfDocumentTypes = useMemo(() => folder.deed?.document_types?.length ?? 0, [folder]); + + return ( +
+
+ {tabs && tabs={tabs} onSelect={setCustomer} />} + {anchorStatus === AnchorStatus.NOT_ANCHORED && ( + + + + )} +
+
+
+ + + + +
+ {doesCustomerHaveDocument ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientSection/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/ClientSection/classes.module.scss similarity index 100% rename from src/front/Components/Layouts/Folder/FolderInformation/ClientSection/classes.module.scss rename to src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/ClientSection/classes.module.scss diff --git a/src/front/Components/Layouts/Folder/FolderInformation/ClientSection/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/ClientSection/index.tsx similarity index 100% rename from src/front/Components/Layouts/Folder/FolderInformation/ClientSection/index.tsx rename to src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/ClientSection/index.tsx diff --git a/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/classes.module.scss new file mode 100644 index 00000000..a274d91e --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/classes.module.scss @@ -0,0 +1,113 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + align-items: center; + flex-direction: column; + min-height: 100%; + + .no-folder-selected { + width: 100%; + + .choose-a-folder { + margin-top: 96px; + text-align: center; + } + } + + .folder-informations { + width: 100%; + min-height: 100%; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: column; + flex-grow: 1; + + .folder-header { + width: 100%; + + .header { + margin-bottom: 32px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + @media (max-width: $screen-m) { + flex-wrap: wrap; + + .title { + margin-bottom: 24px; + } + } + } + } + } + + .second-box { + margin-top: 24px; + margin-bottom: 32px; + } + + .progress-bar { + margin-bottom: 32px; + } + + .button-container { + width: 100%; + display: flex; + gap: 16px; + text-align: center; + justify-content: center; + + .delete-folder { + display: flex; + margin-left: 12px; + } + + @media (max-width: $screen-m) { + display: flex; + flex-direction: column; + + .delete-folder { + margin-left: 0; + margin-top: 12px; + + > * { + flex: 1; + } + } + + > * { + width: 100%; + } + } + } + + .modal-title { + margin-bottom: 24px; + } +} + +.validate-document-container { + .document-validating-container { + .validate-gif { + width: 100%; + height: 100%; + object-fit: contain; + } + } +} + +.loader-container { + display: flex; + flex: 1; + align-items: center; + justify-content: center; + height: 100%; + .loader { + width: 21px; + height: 21px; + } +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/index.tsx new file mode 100644 index 00000000..7d943dd7 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/FolderInformationOld/index.tsx @@ -0,0 +1,541 @@ +import ChevronIcon from "@Assets/Icons/chevron.svg"; +import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; +import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors"; +import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; +import FolderBoxInformation, { EFolderBoxInformationType } from "@Front/Components/DesignSystem/FolderBoxInformation"; +import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; +import QuantityProgressBar from "@Front/Components/DesignSystem/QuantityProgressBar"; +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; +import Module from "@Front/Config/Module"; +import { OfficeFolder } from "le-coffre-resources/dist/Notary"; +import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; +import Link from "next/link"; +import { NextRouter, useRouter } from "next/router"; +import { ChangeEvent, useCallback, useEffect, useState } from "react"; +import Image from "next/image"; +import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif"; + +import BasePage from "../../../Base"; +import classes from "./classes.module.scss"; +import ClientSection from "./ClientSection"; +import Loader from "@Front/Components/DesignSystem/Loader"; +import Newsletter from "@Front/Components/DesignSystem/Newsletter"; +import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; + +export enum AnchorStatus { + "VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN", + "ANCHORING" = "ANCHORING", + "NOT_ANCHORED" = "NOT_ANCHORED", +} + +type IProps = {}; + +type IPropsClass = IProps & { + router: NextRouter; + selectedFolderUid: string; + isAnchored: AnchorStatus; + isLoading: boolean; + selectedFolder: OfficeFolder | null; + getAnchoringStatus: () => Promise; + getFolderCallback: () => Promise; + openedCustomer?: string; +}; + +type IState = { + isArchivedModalOpen: boolean; + inputArchivedDescripton: string; + isValidateModalVisible: boolean; + hasValidateAnchoring: boolean; + isVerifDeleteModalVisible: boolean; + isPreventArchiveModalOpen: boolean; + loadingAnchoring: boolean; +}; +class FolderInformationClass extends BasePage { + public constructor(props: IPropsClass) { + super(props); + this.state = { + isArchivedModalOpen: false, + inputArchivedDescripton: "", + isValidateModalVisible: false, + hasValidateAnchoring: false, + isVerifDeleteModalVisible: false, + isPreventArchiveModalOpen: false, + loadingAnchoring: false, + }; + this.openArchivedModal = this.openArchivedModal.bind(this); + this.closeArchivedModal = this.closeArchivedModal.bind(this); + this.onArchivedModalAccepted = this.onArchivedModalAccepted.bind(this); + this.onPreventArchiveModalAccepted = this.onPreventArchiveModalAccepted.bind(this); + this.getCompletionNumber = this.getCompletionNumber.bind(this); + this.onArchivedDescriptionInputChange = this.onArchivedDescriptionInputChange.bind(this); + this.deleteFolder = this.deleteFolder.bind(this); + this.closeModal = this.closeModal.bind(this); + this.validateAnchoring = this.validateAnchoring.bind(this); + this.openValidateModal = this.openValidateModal.bind(this); + this.openVerifDeleteFolder = this.openVerifDeleteFolder.bind(this); + this.closeVerifDeleteFolder = this.closeVerifDeleteFolder.bind(this); + this.closePreventArchiveModal = this.closePreventArchiveModal.bind(this); + } + + // TODO: Message if the user has not created any folder yet + // TODO: get the selected folder from the api in componentDidMount + public override render(): JSX.Element { + const redirectPathEditCollaborators = Module.getInstance() + .get() + .modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", this.props.selectedFolderUid); + return ( + + {!this.props.isLoading && ( +
+ {this.props.selectedFolder ? ( +
+
+
+
+ Informations du dossier +
+ + + +
+ +
+ +
+
+ +
+ {this.doesFolderHaveCustomer() && ( + + )} +
+ + {!this.doesFolderHaveCustomer() && ( + + )} + +
+ + {this.everyDocumentValidated() && !this.props.isLoading && ( + <> + {this.props.isAnchored === AnchorStatus.NOT_ANCHORED && ( + + )} + {this.props.isAnchored === AnchorStatus.ANCHORING && ( + + )} + {this.props.isAnchored === AnchorStatus.VERIFIED_ON_CHAIN && ( + + )} + + )} + {this.canDeleteFolder() && ( + + + + )} +
+ +
+ Souhaitez-vous vraiment archiver le dossier ? +
+ +
+ +
+ + Vous êtes en train d’archiver le dossier sans avoir l’ancré, êtes-vous sûr de vouloir le faire ? + +
+ +
+ +
+ Cette action sera irréversible. +
+
+ +
+ ) : ( +
+ Informations du dossier +
+ + Sélectionnez un dossier + +
+
+ )} +
+ )} + {this.props.isLoading && ( +
+
+ +
+
+ )} + +
+ {!this.state.hasValidateAnchoring && ( + + Les documents du dossier seront certifiés sur la blockchain. Pensez à bien télécharger l'ensemble des + documents du dossier ainsi que le fichier de preuve d'ancrage pour les mettre dans la GED de votre logiciel + de rédaction d'actes. + + )} + {this.state.hasValidateAnchoring && ( +
+ + Veuillez revenir sur le dossier dans 5 minutes et rafraîchir la page pour télécharger le dossier de + preuve d'ancrage et le glisser dans la GED de votre logiciel de rédaction d'acte. + + +
+ )} +
+
+
+ ); + } + + private closePreventArchiveModal() { + this.setState({ + isPreventArchiveModalOpen: false, + }); + } + + public openVerifDeleteFolder() { + this.setState({ + isVerifDeleteModalVisible: true, + }); + } + + public closeVerifDeleteFolder() { + this.setState({ + isVerifDeleteModalVisible: false, + }); + } + + private closeModal() { + this.setState({ + isValidateModalVisible: false, + }); + } + + private openValidateModal() { + this.setState({ + isValidateModalVisible: true, + }); + } + + private async validateAnchoring() { + this.setState({ + hasValidateAnchoring: true, + }); + + try { + const timeoutDelay = 9800; + await this.anchorFolder(); + setTimeout(() => { + this.setState({ + isValidateModalVisible: false, + }); + }, timeoutDelay); + + setTimeout(() => { + this.setState({ + hasValidateAnchoring: false, + }); + }, timeoutDelay + 1000); + } catch (e) { + this.setState({ + isValidateModalVisible: false, + hasValidateAnchoring: false, + }); + console.error(e); + } + } + + private async anchorFolder() { + if (!this.props.selectedFolder?.uid) return; + await OfficeFolderAnchors.getInstance().post(this.props.selectedFolder.uid); + this.props.getAnchoringStatus(); + } + + private async downloadAnchoringProof(uid?: string) { + if (!uid) return; + this.setState({ loadingAnchoring: true }); + try { + const file: Blob = await OfficeFolderAnchors.getInstance().download(uid); + const url = window.URL.createObjectURL(file); + const a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + // the filename you want + a.download = `anchoring_proof_${this.props.selectedFolder?.folder_number}_${this.props.selectedFolder?.name}.zip`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + this.setState({ loadingAnchoring: false }); + } catch (e) { + this.setState({ loadingAnchoring: false }); + console.error(e); + } + } + + private everyDocumentValidated(): boolean { + if (!this.props.selectedFolder?.documents) return false; + return ( + this.props.selectedFolder?.documents?.length >= 1 && + this.props.selectedFolder?.documents.every((document) => document.document_status === EDocumentStatus.VALIDATED) + ); + } + + private async deleteFolder() { + if (!this.props.selectedFolder?.uid) return; + await Folders.getInstance().delete(this.props.selectedFolder.uid); + this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path); + } + + private getCompletionNumber() { + const documents = this.props.selectedFolder?.documents; + if (!documents) return 0; + const totalDocuments = documents.length; + const refusedDocuments = documents.filter((document) => document.document_status === EDocumentStatus.REFUSED).length ?? 0; + const askedDocuments = + documents.filter( + (document) => document.document_status === EDocumentStatus.ASKED || document.document_status === EDocumentStatus.DEPOSITED, + ).length ?? 0; + const depositedDocuments = totalDocuments - askedDocuments - refusedDocuments; + const percentage = (depositedDocuments / totalDocuments) * 100; + return isNaN(percentage) ? 0 : percentage; + } + + private doesFolderHaveCustomer(): boolean { + if (!this.props.selectedFolder?.customers) return false; + return this.props.selectedFolder?.customers!.length > 0; + } + + private canDeleteFolder(): boolean { + return (this.props.selectedFolder?.customers?.length ?? 0) === 0 && (this.props.selectedFolder?.documents?.length ?? 0) === 0; + } + + private openArchivedModal(): void { + if (this.everyDocumentValidated() && this.props.isAnchored === AnchorStatus.VERIFIED_ON_CHAIN) { + this.setState({ isArchivedModalOpen: true }); + } else { + this.setState({ isPreventArchiveModalOpen: true }); + } + } + + private closeArchivedModal(): void { + this.setState({ isArchivedModalOpen: false }); + } + + private onArchivedDescriptionInputChange(e: ChangeEvent) { + this.setState({ inputArchivedDescripton: e.target.value }); + } + + private async onArchivedModalAccepted() { + if (!this.props.selectedFolder) return; + await Folders.getInstance().archive(this.props.selectedFolder.uid ?? "", this.state.inputArchivedDescripton); + this.closeArchivedModal(); + this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path); + } + + private async onPreventArchiveModalAccepted() { + if (!this.props.selectedFolder) return; + await Folders.getInstance().archive(this.props.selectedFolder.uid ?? "", this.state.inputArchivedDescripton); + this.closePreventArchiveModal(); + this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path); + } +} + +export default function FolderInformation(props: IProps) { + const router = useRouter(); + const [isAnchored, setIsAnchored] = useState(AnchorStatus.NOT_ANCHORED); + const [isLoading, setIsLoading] = useState(true); + const [selectedFolder, setSelectedFolder] = useState(null); + + let { folderUid } = router.query; + const customerUid = router.query["customerUid"] as string | undefined; + folderUid = folderUid as string; + + const getAnchoringStatus = useCallback(async () => { + if (!folderUid) return; + try { + const anchorStatus = await OfficeFolderAnchors.getInstance().getByUid(folderUid as string); + setIsAnchored(anchorStatus.status === "VERIFIED_ON_CHAIN" ? AnchorStatus.VERIFIED_ON_CHAIN : AnchorStatus.ANCHORING); + } catch (e) { + setIsAnchored(AnchorStatus.NOT_ANCHORED); + } + }, [folderUid]); + + const getFolder = useCallback(async () => { + if (!folderUid) return; + setIsLoading(true); + const query = { + q: { + deed: { include: { deed_type: true } }, + office: true, + customers: { + include: { + contact: true, + documents: { + include: { + folder: true, + document_type: true, + files: true, + }, + }, + }, + }, + documents: { + include: { + depositor: { + include: { + contact: true, + }, + }, + }, + }, + folder_anchor: true, + notes: { + include: { + customer: true, + }, + }, + }, + }; + + const folder = await Folders.getInstance().getByUid(folderUid as string, query); + if (folder) { + setSelectedFolder(folder); + getAnchoringStatus(); + } + + setIsLoading(false); + }, [folderUid, getAnchoringStatus]); + + useEffect(() => { + setIsLoading(true); + getFolder(); + }, [getFolder]); + + return ( + + ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/classes.module.scss new file mode 100644 index 00000000..5abf5a01 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/classes.module.scss @@ -0,0 +1,39 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + gap: var(--spacing-lg, 40px); + .info-box1 { + display: flex; + width: 648px; + flex-direction: column; + gap: var(--spacing-sm, 8px); + + .open-date { + display: flex; + gap: var(--spacing-sm, 8px); + } + } + + .info-box2 { + display: flex; + flex-direction: column; + gap: var(--spacing-lg, 24px); + .progress-container { + display: flex; + justify-content: space-between; + align-items: center; + + .icon-container { + display: flex; + gap: var(--spacing-md, 8px); + } + } + } + + .separator { + background-color: var(--separator-stroke-light); + width: 1px; + align-self: stretch; + } +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx new file mode 100644 index 00000000..e6a6656c --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/InformationSection/index.tsx @@ -0,0 +1,71 @@ +import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; +import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag"; +import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; +import { UserGroupIcon, PencilSquareIcon, ArchiveBoxIcon } from "@heroicons/react/24/outline"; +import { OfficeFolder } from "le-coffre-resources/dist/Notary"; +import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; +import { useCallback } from "react"; + +import classes from "./classes.module.scss"; +import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton"; + +type IProps = { + folder: OfficeFolder | null; +}; + +export default function InformationSection(props: IProps) { + const { folder } = props; + const getCompletionNumber = useCallback(() => { + const documents = folder?.documents; + if (!documents) return 0; + const totalDocuments = documents.length; + const refusedDocuments = documents.filter((document) => document.document_status === EDocumentStatus.REFUSED).length ?? 0; + const askedDocuments = + documents.filter( + (document) => document.document_status === EDocumentStatus.ASKED || document.document_status === EDocumentStatus.DEPOSITED, + ).length ?? 0; + const depositedDocuments = totalDocuments - askedDocuments - refusedDocuments; + const percentage = (depositedDocuments / totalDocuments) * 100; + return isNaN(percentage) ? 0 : percentage; + }, [folder]); + + return ( +
+
+
+ {folder?.folder_number} + {folder?.name} +
+ + +
+ Ouverture du dossier + + {folder?.created_at ? new Date(folder.created_at).toLocaleDateString() : ""} + +
+
+
+ +
+
+ +
+ } variant={EIconButtonVariant.NEUTRAL} /> + } + variant={EIconButtonVariant.NEUTRAL} + /> + } variant={EIconButtonVariant.ERROR} /> +
+
+
+ + Notre dossier + + Travaux de rénovation en cours. +
+
+
+ ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/AddClientSection/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/AddClientSection/index.tsx new file mode 100644 index 00000000..3d9f7da1 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/AddClientSection/index.tsx @@ -0,0 +1,34 @@ +import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; +import EmptyAlert from "@Front/Components/DesignSystem/EmptyAlert"; +import Module from "@Front/Config/Module"; +import { UserPlusIcon } from "@heroicons/react/24/outline"; +import Link from "next/link"; +import React, { useMemo } from "react"; + +type IProps = { + folderUid: string; +}; + +export default function AddClientSection(props: IProps) { + const { folderUid } = props; + + const addClientPath = useMemo(() => { + if (!folderUid) return ""; + return Module.getInstance().get().modules.pages.Folder.pages.AddClient.props.path.replace("[folderUid]", folderUid); + }, [folderUid]); + + return ( + } + title="Ajouter des clients au dossier" + description="Pour pouvoir faire une demande de document, vous devez d'abord ajouter un ou plusieurs clients à ce dossier. Cette étape est essentielle pour assurer le suivi et la gestion des documents." + footer={ + + + + } + /> + ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/DeleteFolderModal/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/DeleteFolderModal/index.tsx new file mode 100644 index 00000000..4e2e74c1 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/DeleteFolderModal/index.tsx @@ -0,0 +1,42 @@ +import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; +import Modal from "@Front/Components/DesignSystem/Modal"; +import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; +import Module from "@Front/Config/Module"; +import { OfficeFolder } from "le-coffre-resources/dist/Notary"; +import { useRouter } from "next/router"; +import React, { useCallback } from "react"; + +type IProps = { + isOpen: boolean; + onClose?: () => void; + folder: OfficeFolder; +}; + +export default function DeleteFolderModal(props: IProps) { + const { isOpen, onClose, folder } = props; + const navigate = useRouter(); + + const onDelete = useCallback(() => { + if (!folder.uid) return; + if ((folder?.customers?.length ?? 0) > 0 || (folder?.documents?.length ?? 0) > 0) + return console.warn("Cannot delete folder with customers or documents"); + + return Folders.getInstance() + .delete(folder.uid) + .then(() => navigate.push(Module.getInstance().get().modules.pages.Folder.props.path)) + .then(onClose); + }, [folder, navigate, onClose]); + + return ( + + + Cette action est irréversible. En supprimant ce dossier, toutes les informations associées seront définitivement perdues. + + + ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/classes.module.scss new file mode 100644 index 00000000..9c8a3edd --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/classes.module.scss @@ -0,0 +1,11 @@ +@import "@Themes/constants.scss"; + +.root { + display: flex; + flex-direction: column; + gap: var(--spacing-xl, 32px); + + .delete-button { + align-self: flex-end; + } +} \ No newline at end of file diff --git a/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/index.tsx new file mode 100644 index 00000000..3273eeb6 --- /dev/null +++ b/src/front/Components/Layouts/Folder/FolderInformation/NoClientView/index.tsx @@ -0,0 +1,41 @@ +import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { OfficeFolder } from "le-coffre-resources/dist/Notary"; +import { useMemo } from "react"; + +import AddClientSection from "./AddClientSection"; +import classes from "./classes.module.scss"; +import DeleteFolderModal from "./DeleteFolderModal"; +import useOpenable from "@Front/Hooks/useOpenable"; +import { AnchorStatus } from ".."; + +type IProps = { + folder: OfficeFolder; + anchorStatus: AnchorStatus; +}; + +export default function NoClientView(props: IProps) { + const { folder, anchorStatus } = props; + + const deleteFolderModal = useOpenable(); + const canDeleteFolder = useMemo(() => folder.documents?.length === 0 && folder.customers?.length === 0, [folder]); + + return ( +
+ {anchorStatus === AnchorStatus.NOT_ANCHORED && } + {canDeleteFolder && ( + <> + + + + )} +
+ ); +} diff --git a/src/front/Components/Layouts/Folder/FolderInformation/classes.module.scss b/src/front/Components/Layouts/Folder/FolderInformation/classes.module.scss index a274d91e..82b505c7 100644 --- a/src/front/Components/Layouts/Folder/FolderInformation/classes.module.scss +++ b/src/front/Components/Layouts/Folder/FolderInformation/classes.module.scss @@ -2,91 +2,13 @@ .root { display: flex; - align-items: center; flex-direction: column; - min-height: 100%; + gap: var(--spacing-xl, 32px); - .no-folder-selected { - width: 100%; - - .choose-a-folder { - margin-top: 96px; - text-align: center; - } - } - - .folder-informations { - width: 100%; - min-height: 100%; - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: column; - flex-grow: 1; - - .folder-header { - width: 100%; - - .header { - margin-bottom: 32px; - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - - @media (max-width: $screen-m) { - flex-wrap: wrap; - - .title { - margin-bottom: 24px; - } - } - } - } - } - - .second-box { - margin-top: 24px; - margin-bottom: 32px; - } - - .progress-bar { - margin-bottom: 32px; - } - - .button-container { - width: 100%; - display: flex; - gap: 16px; - text-align: center; - justify-content: center; - - .delete-folder { - display: flex; - margin-left: 12px; - } - - @media (max-width: $screen-m) { - display: flex; - flex-direction: column; - - .delete-folder { - margin-left: 0; - margin-top: 12px; - - > * { - flex: 1; - } - } - - > * { - width: 100%; - } - } - } - - .modal-title { - margin-bottom: 24px; + .separator { + background-color: var(--separator-stroke-light); + width: 1px; + align-self: stretch; } } diff --git a/src/front/Components/Layouts/Folder/FolderInformation/index.tsx b/src/front/Components/Layouts/Folder/FolderInformation/index.tsx index 5d0d988e..07c80935 100644 --- a/src/front/Components/Layouts/Folder/FolderInformation/index.tsx +++ b/src/front/Components/Layouts/Folder/FolderInformation/index.tsx @@ -1,27 +1,15 @@ -import ChevronIcon from "@Assets/Icons/chevron.svg"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import OfficeFolderAnchors from "@Front/Api/LeCoffreApi/Notary/OfficeFolderAnchors/OfficeFolderAnchors"; -import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; -import FolderBoxInformation, { EFolderBoxInformationType } from "@Front/Components/DesignSystem/FolderBoxInformation"; -import Confirm from "@Front/Components/DesignSystem/OldModal/Confirm"; -import QuantityProgressBar from "@Front/Components/DesignSystem/QuantityProgressBar"; -import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; -import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; -import Module from "@Front/Config/Module"; -import { OfficeFolder } from "le-coffre-resources/dist/Notary"; -import { EDocumentStatus } from "le-coffre-resources/dist/Notary/Document"; -import Link from "next/link"; -import { NextRouter, useRouter } from "next/router"; -import { ChangeEvent, useCallback, useEffect, useState } from "react"; -import Image from "next/image"; -import ValidateAnchoringGif from "@Front/Assets/images/validate_anchoring.gif"; - -import BasePage from "../../Base"; -import classes from "./classes.module.scss"; -import ClientSection from "./ClientSection"; import Loader from "@Front/Components/DesignSystem/Loader"; -import Newsletter from "@Front/Components/DesignSystem/Newsletter"; -import TextAreaField from "@Front/Components/DesignSystem/Form/TextareaField"; +import DefaultNotaryDashboard from "@Front/Components/LayoutTemplates/DefaultNotaryDashboard"; +import { OfficeFolder } from "le-coffre-resources/dist/Notary"; +import { useParams } from "next/navigation"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import classes from "./classes.module.scss"; +import InformationSection from "./InformationSection"; +import NoClientView from "./NoClientView"; +import ClientView from "./ClientView"; export enum AnchorStatus { "VERIFIED_ON_CHAIN" = "VERIFIED_ON_CHAIN", @@ -31,455 +19,19 @@ export enum AnchorStatus { type IProps = {}; -type IPropsClass = IProps & { - router: NextRouter; - selectedFolderUid: string; - isAnchored: AnchorStatus; - isLoading: boolean; - selectedFolder: OfficeFolder | null; - getAnchoringStatus: () => Promise; - getFolderCallback: () => Promise; - openedCustomer?: string; -}; - -type IState = { - isArchivedModalOpen: boolean; - inputArchivedDescripton: string; - isValidateModalVisible: boolean; - hasValidateAnchoring: boolean; - isVerifDeleteModalVisible: boolean; - isPreventArchiveModalOpen: boolean; - loadingAnchoring: boolean; -}; -class FolderInformationClass extends BasePage { - public constructor(props: IPropsClass) { - super(props); - this.state = { - isArchivedModalOpen: false, - inputArchivedDescripton: "", - isValidateModalVisible: false, - hasValidateAnchoring: false, - isVerifDeleteModalVisible: false, - isPreventArchiveModalOpen: false, - loadingAnchoring: false, - }; - this.openArchivedModal = this.openArchivedModal.bind(this); - this.closeArchivedModal = this.closeArchivedModal.bind(this); - this.onArchivedModalAccepted = this.onArchivedModalAccepted.bind(this); - this.onPreventArchiveModalAccepted = this.onPreventArchiveModalAccepted.bind(this); - this.getCompletionNumber = this.getCompletionNumber.bind(this); - this.onArchivedDescriptionInputChange = this.onArchivedDescriptionInputChange.bind(this); - this.deleteFolder = this.deleteFolder.bind(this); - this.closeModal = this.closeModal.bind(this); - this.validateAnchoring = this.validateAnchoring.bind(this); - this.openValidateModal = this.openValidateModal.bind(this); - this.openVerifDeleteFolder = this.openVerifDeleteFolder.bind(this); - this.closeVerifDeleteFolder = this.closeVerifDeleteFolder.bind(this); - this.closePreventArchiveModal = this.closePreventArchiveModal.bind(this); - } - - // TODO: Message if the user has not created any folder yet - // TODO: get the selected folder from the api in componentDidMount - public override render(): JSX.Element { - const redirectPathEditCollaborators = Module.getInstance() - .get() - .modules.pages.Folder.pages.EditCollaborators.props.path.replace("[folderUid]", this.props.selectedFolderUid); - return ( - - {!this.props.isLoading && ( -
- {this.props.selectedFolder ? ( -
-
-
-
- Informations du dossier -
- - - -
- -
- -
-
- -
- {this.doesFolderHaveCustomer() && ( - - )} -
- - {!this.doesFolderHaveCustomer() && ( - - )} - -
- - {this.everyDocumentValidated() && !this.props.isLoading && ( - <> - {this.props.isAnchored === AnchorStatus.NOT_ANCHORED && ( - - )} - {this.props.isAnchored === AnchorStatus.ANCHORING && ( - - )} - {this.props.isAnchored === AnchorStatus.VERIFIED_ON_CHAIN && ( - - )} - - )} - {this.canDeleteFolder() && ( - - - - )} -
- -
- Souhaitez-vous vraiment archiver le dossier ? -
- -
- -
- - Vous êtes en train d’archiver le dossier sans avoir l’ancré, êtes-vous sûr de vouloir le faire ? - -
- -
- -
- Cette action sera irréversible. -
-
- -
- ) : ( -
- Informations du dossier -
- - Sélectionnez un dossier - -
-
- )} -
- )} - {this.props.isLoading && ( -
-
- -
-
- )} - -
- {!this.state.hasValidateAnchoring && ( - - Les documents du dossier seront certifiés sur la blockchain. Pensez à bien télécharger l'ensemble des - documents du dossier ainsi que le fichier de preuve d'ancrage pour les mettre dans la GED de votre logiciel - de rédaction d'actes. - - )} - {this.state.hasValidateAnchoring && ( -
- - Veuillez revenir sur le dossier dans 5 minutes et rafraîchir la page pour télécharger le dossier de - preuve d'ancrage et le glisser dans la GED de votre logiciel de rédaction d'acte. - - -
- )} -
-
-
- ); - } - - private closePreventArchiveModal() { - this.setState({ - isPreventArchiveModalOpen: false, - }); - } - - public openVerifDeleteFolder() { - this.setState({ - isVerifDeleteModalVisible: true, - }); - } - - public closeVerifDeleteFolder() { - this.setState({ - isVerifDeleteModalVisible: false, - }); - } - - private closeModal() { - this.setState({ - isValidateModalVisible: false, - }); - } - - private openValidateModal() { - this.setState({ - isValidateModalVisible: true, - }); - } - - private async validateAnchoring() { - this.setState({ - hasValidateAnchoring: true, - }); - - try { - const timeoutDelay = 9800; - await this.anchorFolder(); - setTimeout(() => { - this.setState({ - isValidateModalVisible: false, - }); - }, timeoutDelay); - - setTimeout(() => { - this.setState({ - hasValidateAnchoring: false, - }); - }, timeoutDelay + 1000); - } catch (e) { - this.setState({ - isValidateModalVisible: false, - hasValidateAnchoring: false, - }); - console.error(e); - } - } - - private async anchorFolder() { - if (!this.props.selectedFolder?.uid) return; - await OfficeFolderAnchors.getInstance().post(this.props.selectedFolder.uid); - this.props.getAnchoringStatus(); - } - - private async downloadAnchoringProof(uid?: string) { - if (!uid) return; - this.setState({ loadingAnchoring: true }); - try { - const file: Blob = await OfficeFolderAnchors.getInstance().download(uid); - const url = window.URL.createObjectURL(file); - const a = document.createElement("a"); - a.style.display = "none"; - a.href = url; - // the filename you want - a.download = `anchoring_proof_${this.props.selectedFolder?.folder_number}_${this.props.selectedFolder?.name}.zip`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - this.setState({ loadingAnchoring: false }); - } catch (e) { - this.setState({ loadingAnchoring: false }); - console.error(e); - } - } - - private everyDocumentValidated(): boolean { - if (!this.props.selectedFolder?.documents) return false; - return ( - this.props.selectedFolder?.documents?.length >= 1 && - this.props.selectedFolder?.documents.every((document) => document.document_status === EDocumentStatus.VALIDATED) - ); - } - - private async deleteFolder() { - if (!this.props.selectedFolder?.uid) return; - await Folders.getInstance().delete(this.props.selectedFolder.uid); - this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path); - } - - private getCompletionNumber() { - const documents = this.props.selectedFolder?.documents; - if (!documents) return 0; - const totalDocuments = documents.length; - const refusedDocuments = documents.filter((document) => document.document_status === EDocumentStatus.REFUSED).length ?? 0; - const askedDocuments = - documents.filter( - (document) => document.document_status === EDocumentStatus.ASKED || document.document_status === EDocumentStatus.DEPOSITED, - ).length ?? 0; - const depositedDocuments = totalDocuments - askedDocuments - refusedDocuments; - const percentage = (depositedDocuments / totalDocuments) * 100; - return isNaN(percentage) ? 0 : percentage; - } - - private doesFolderHaveCustomer(): boolean { - if (!this.props.selectedFolder?.customers) return false; - return this.props.selectedFolder?.customers!.length > 0; - } - - private canDeleteFolder(): boolean { - return (this.props.selectedFolder?.customers?.length ?? 0) === 0 && (this.props.selectedFolder?.documents?.length ?? 0) === 0; - } - - private openArchivedModal(): void { - if (this.everyDocumentValidated() && this.props.isAnchored === AnchorStatus.VERIFIED_ON_CHAIN) { - this.setState({ isArchivedModalOpen: true }); - } else { - this.setState({ isPreventArchiveModalOpen: true }); - } - } - - private closeArchivedModal(): void { - this.setState({ isArchivedModalOpen: false }); - } - - private onArchivedDescriptionInputChange(e: ChangeEvent) { - this.setState({ inputArchivedDescripton: e.target.value }); - } - - private async onArchivedModalAccepted() { - if (!this.props.selectedFolder) return; - await Folders.getInstance().archive(this.props.selectedFolder.uid ?? "", this.state.inputArchivedDescripton); - this.closeArchivedModal(); - this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path); - } - - private async onPreventArchiveModalAccepted() { - if (!this.props.selectedFolder) return; - await Folders.getInstance().archive(this.props.selectedFolder.uid ?? "", this.state.inputArchivedDescripton); - this.closePreventArchiveModal(); - this.props.router.push(Module.getInstance().get().modules.pages.Folder.props.path); - } -} - export default function FolderInformation(props: IProps) { - const router = useRouter(); - const [isAnchored, setIsAnchored] = useState(AnchorStatus.NOT_ANCHORED); + const [anchorStatus, setAnchorStatus] = useState(AnchorStatus.NOT_ANCHORED); const [isLoading, setIsLoading] = useState(true); - const [selectedFolder, setSelectedFolder] = useState(null); + const [folder, setFolder] = useState(null); - let { folderUid } = router.query; - const customerUid = router.query["customerUid"] as string | undefined; - folderUid = folderUid as string; + const params = useParams(); + const folderUid = params["folderUid"] as string; - const getAnchoringStatus = useCallback(async () => { + const fetchFolder = useCallback(async () => { if (!folderUid) return; - try { - const anchorStatus = await OfficeFolderAnchors.getInstance().getByUid(folderUid as string); - setIsAnchored(anchorStatus.status === "VERIFIED_ON_CHAIN" ? AnchorStatus.VERIFIED_ON_CHAIN : AnchorStatus.ANCHORING); - } catch (e) { - setIsAnchored(AnchorStatus.NOT_ANCHORED); - } - }, [folderUid]); - - const getFolder = useCallback(async () => { - if (!folderUid) return; - setIsLoading(true); const query = { q: { - deed: { include: { deed_type: true } }, + deed: { include: { deed_type: true, document_types: true } }, office: true, customers: { include: { @@ -511,31 +63,46 @@ export default function FolderInformation(props: IProps) { }, }; - const folder = await Folders.getInstance().getByUid(folderUid as string, query); - if (folder) { - setSelectedFolder(folder); - getAnchoringStatus(); - } + return Folders.getInstance() + .getByUid(folderUid, query) + .then((folder) => setFolder(folder)); + }, [folderUid]); - setIsLoading(false); - }, [folderUid, getAnchoringStatus]); + const fetchAnchorStatus = useCallback(() => { + return OfficeFolderAnchors.getInstance() + .getByUid(folderUid) + .then((anchorStatus) => + setAnchorStatus(anchorStatus.status === "VERIFIED_ON_CHAIN" ? AnchorStatus.VERIFIED_ON_CHAIN : AnchorStatus.ANCHORING), + ) + .catch(() => setAnchorStatus(AnchorStatus.NOT_ANCHORED)); + }, [folderUid]); useEffect(() => { setIsLoading(true); - getFolder(); - }, [getFolder]); + fetchFolder() + .then(() => fetchAnchorStatus()) + .catch((e) => console.error(e)) + .finally(() => setIsLoading(false)); + }, [fetchAnchorStatus, fetchFolder, folderUid]); + + const doesFolderHaveClient = useMemo(() => folder?.customers?.length !== 0, [folder]); return ( - + + {!isLoading && ( +
+ + {folder && !doesFolderHaveClient && } + {folder && doesFolderHaveClient && } +
+ )} + {isLoading && ( +
+
+ +
+
+ )} +
); }