Merge branch 'dev' into staging

This commit is contained in:
Max S 2024-09-13 13:26:29 +02:00
commit 87125f0d7f
9 changed files with 93 additions and 65 deletions

View File

@ -8,6 +8,7 @@ export interface IGetFoldersParams {
select?: {}; select?: {};
where?: {}; where?: {};
include?: {}; include?: {};
orderBy?: {};
}; };
} }

View File

@ -1,5 +1,6 @@
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents"; import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
@ -18,6 +19,7 @@ export default function DeleteAskedDocumentModal(props: IProps) {
Documents.getInstance() Documents.getInstance()
.delete(documentUid) .delete(documentUid)
.then(() => onDeleteSuccess(documentUid)) .then(() => onDeleteSuccess(documentUid))
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
.then(onClose) .then(onClose)
.catch((error) => console.warn(error)), .catch((error) => console.warn(error)),
[documentUid, onClose, onDeleteSuccess], [documentUid, onClose, onDeleteSuccess],

View File

@ -1,5 +1,6 @@
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary"; import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
@ -18,6 +19,7 @@ export default function DeleteSentDocumentModal(props: IProps) {
DocumentsNotary.getInstance() DocumentsNotary.getInstance()
.delete(documentUid) .delete(documentUid)
.then(() => onDeleteSuccess(documentUid)) .then(() => onDeleteSuccess(documentUid))
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "Le document a été supprimé avec succès." }))
.then(onClose) .then(onClose)
.catch((error) => console.warn(error)), .catch((error) => console.warn(error)),
[documentUid, onClose, onDeleteSuccess], [documentUid, onClose, onDeleteSuccess],

View File

@ -1,4 +1,7 @@
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files"; import Files from "@Front/Api/LeCoffreApi/Notary/Files/Files";
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
import CircleProgress from "@Front/Components/DesignSystem/CircleProgress"; import CircleProgress from "@Front/Components/DesignSystem/CircleProgress";
import IconButton from "@Front/Components/DesignSystem/IconButton"; import IconButton from "@Front/Components/DesignSystem/IconButton";
import Table from "@Front/Components/DesignSystem/Table"; import Table from "@Front/Components/DesignSystem/Table";
@ -15,14 +18,13 @@ import { DocumentNotary } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import NoDocument from "../NoDocument";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal"; import DeleteAskedDocumentModal from "./DeleteAskedDocumentModal";
import DeleteSentDocumentModal from "./DeleteSentDocumentModal"; import DeleteSentDocumentModal from "./DeleteSentDocumentModal";
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
type IProps = { type IProps = {
documents: Document[]; customerUid: string;
documentsNotary: DocumentNotary[];
folderUid: string; folderUid: string;
}; };
@ -34,23 +36,46 @@ const tradDocumentStatus: Record<EDocumentStatus, string> = {
}; };
export default function DocumentTables(props: IProps) { export default function DocumentTables(props: IProps) {
const { documents: documentsProps, folderUid, documentsNotary } = props; const { folderUid, customerUid } = props;
const [documents, setDocuments] = useState<Document[]>(documentsProps); const [documents, setDocuments] = useState<Document[]>([]);
const [documentUid, setDocumentUid] = useState<string | null>(null); const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
const [focusedDocumentUid, setFocusedDocumentUid] = useState<string | null>(null);
const isMobile = useMediaQuery("(max-width:524px)"); const isMobile = useMediaQuery("(max-width:524px)");
const deleteAskedDocumentModal = useOpenable(); const deleteAskedDocumentModal = useOpenable();
const deleteSentDocumentModal = useOpenable(); const deleteSentDocumentModal = useOpenable();
const fetchDocuments = useCallback(
() =>
Documents.getInstance()
.get({
where: { folder: { uid: folderUid }, depositor: { uid: customerUid } },
include: { files: true, document_type: true },
})
.then(setDocuments)
.catch(console.warn),
[customerUid, folderUid],
);
const fetchDocumentsNotary = useCallback(
() =>
DocumentsNotary.getInstance()
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
.then(setDocumentsNotary)
.catch(console.warn),
[customerUid, folderUid],
);
useEffect(() => { useEffect(() => {
setDocuments(documentsProps); fetchDocuments();
}, [documentsProps]); fetchDocumentsNotary();
}, [fetchDocuments, fetchDocumentsNotary]);
const openDeleteAskedDocumentModal = useCallback( const openDeleteAskedDocumentModal = useCallback(
(uid: string | undefined) => { (uid: string | undefined) => {
if (!uid) return; if (!uid) return;
setDocumentUid(uid); setFocusedDocumentUid(uid);
deleteAskedDocumentModal.open(); deleteAskedDocumentModal.open();
}, },
[deleteAskedDocumentModal], [deleteAskedDocumentModal],
@ -59,7 +84,7 @@ export default function DocumentTables(props: IProps) {
const openDeleteSentDocumentModal = useCallback( const openDeleteSentDocumentModal = useCallback(
(uid: string | undefined) => { (uid: string | undefined) => {
if (!uid) return; if (!uid) return;
setDocumentUid(uid); setFocusedDocumentUid(uid);
deleteSentDocumentModal.open(); deleteSentDocumentModal.open();
}, },
[deleteSentDocumentModal], [deleteSentDocumentModal],
@ -237,9 +262,7 @@ export default function DocumentTables(props: IProps) {
return (validatedDocuments.length / total) * 100; return (validatedDocuments.length / total) * 100;
}, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]); }, [askedDocuments.length, refusedDocuments.length, toValidateDocuments.length, validatedDocuments.length]);
const handleDelete = useCallback((_documentUid: string) => { if (documents.length === 0 && documentsNotary.length === 0) return <NoDocument />;
window.location.reload();
}, []);
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
@ -254,19 +277,19 @@ export default function DocumentTables(props: IProps) {
{validatedDocuments.length > 0 && <Table header={getHeader("Validé le", isMobile)} rows={validatedDocuments} />} {validatedDocuments.length > 0 && <Table header={getHeader("Validé le", isMobile)} rows={validatedDocuments} />}
{refusedDocuments.length > 0 && <Table header={getHeader("Demandé le", isMobile)} rows={refusedDocuments} />} {refusedDocuments.length > 0 && <Table header={getHeader("Demandé le", isMobile)} rows={refusedDocuments} />}
{sentDocuments.length > 0 && <Table header={getHeader("Envoyé le", isMobile)} rows={sentDocuments} />} {sentDocuments.length > 0 && <Table header={getHeader("Envoyé le", isMobile)} rows={sentDocuments} />}
{documentUid && ( {focusedDocumentUid && (
<> <>
<DeleteAskedDocumentModal <DeleteAskedDocumentModal
isOpen={deleteAskedDocumentModal.isOpen} isOpen={deleteAskedDocumentModal.isOpen}
onClose={deleteAskedDocumentModal.close} onClose={deleteAskedDocumentModal.close}
onDeleteSuccess={handleDelete} onDeleteSuccess={fetchDocuments}
documentUid={documentUid} documentUid={focusedDocumentUid}
/> />
<DeleteSentDocumentModal <DeleteSentDocumentModal
isOpen={deleteSentDocumentModal.isOpen} isOpen={deleteSentDocumentModal.isOpen}
onClose={deleteSentDocumentModal.close} onClose={deleteSentDocumentModal.close}
onDeleteSuccess={handleDelete} onDeleteSuccess={fetchDocumentsNotary}
documentUid={documentUid} documentUid={focusedDocumentUid}
/> />
</> </>
)} )}

View File

@ -1,13 +1,15 @@
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
import CheckBox from "@Front/Components/DesignSystem/CheckBox"; import CheckBox from "@Front/Components/DesignSystem/CheckBox";
import { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld"; import { IOption } from "@Front/Components/DesignSystem/Form/SelectFieldOld";
import Modal from "@Front/Components/DesignSystem/Modal"; import Modal from "@Front/Components/DesignSystem/Modal";
import Separator, { ESeperatorColor } from "@Front/Components/DesignSystem/Separator"; import Separator, { ESeperatorColor } from "@Front/Components/DesignSystem/Separator";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography"; import Typography, { ETypo } from "@Front/Components/DesignSystem/Typography";
import Customer from "le-coffre-resources/dist/Customer"; import Customer from "le-coffre-resources/dist/Customer";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import React, { useCallback, useMemo, useState } from "react"; import React, { useCallback, useMemo, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
type IProps = { type IProps = {
isOpen: boolean; isOpen: boolean;
@ -22,19 +24,23 @@ export default function ReminderModal(props: IProps) {
const [isAllSelected, setIsAllSelected] = useState(false); const [isAllSelected, setIsAllSelected] = useState(false);
const onRemind = useCallback(() => { const onRemind = useCallback(() => {
Customers.getInstance().sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[]); Customers.getInstance()
onRemindSuccess(); .sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[])
onClose?.(); .then(onRemindSuccess)
.then(() => ToasterService.getInstance().success({ title: "Succès !", description: "La relance a été envoyée avec succès." }))
.then(onClose);
}, [customer.uid, onClose, onRemindSuccess, selectedOptions]); }, [customer.uid, onClose, onRemindSuccess, selectedOptions]);
const documentsOptions: IOption[] = useMemo( const documentsOptions: IOption[] = useMemo(
() => () =>
customer.documents?.map((document) => { customer.documents
return { ?.filter((document) => document.document_status !== EDocumentStatus.VALIDATED)
label: document.document_type?.name ?? "", .map((document) => {
value: document.uid ?? "", return {
}; label: document.document_type?.name ?? "",
}) ?? [], value: document.uid ?? "",
};
}) ?? [],
[customer], [customer],
); );

View File

@ -9,39 +9,55 @@ import Customer from "le-coffre-resources/dist/Customer";
import { DocumentReminder } from "le-coffre-resources/dist/Notary"; import { DocumentReminder } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import ReminderModal from "./ReminderModal"; import ReminderModal from "./ReminderModal";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
type IProps = { type IProps = {
customer: Customer; customer: Customer;
doesCustomerHaveDocument: boolean;
isAnchored: boolean; isAnchored: boolean;
}; };
export default function EmailReminder(props: IProps) { export default function EmailReminder(props: IProps) {
const { customer, doesCustomerHaveDocument, isAnchored } = props; const { customer, isAnchored } = props;
const [reminders, setReminders] = useState<DocumentReminder[] | null>(null); const [reminders, setReminders] = useState<DocumentReminder[] | null>(null);
const { isOpen, open, close } = useOpenable(); const { isOpen, open, close } = useOpenable();
const router = useRouter(); const router = useRouter();
let { folderUid } = router.query;
const fetchReminders = useCallback(async () => { const fetchReminders = useCallback(async () => {
if (!customer.uid || !folderUid) return;
DocumentReminders.getInstance() DocumentReminders.getInstance()
.get({ .get({
where: { document: { depositor: { uid: customer.uid } } }, where: { document: { depositor: { uid: customer.uid }, folder: { uid: folderUid } } },
include: { document: "true" }, include: { document: "true" },
orderBy: { reminder_date: "desc" }, orderBy: { reminder_date: "desc" },
}) })
.then((reminders) => setReminders(reminders)) .then((reminders) => setReminders(reminders))
.catch((e) => console.warn(e)); .catch((e) => console.warn(e));
}, [customer.uid]); }, [customer.uid, folderUid]);
useEffect(() => { useEffect(() => {
fetchReminders(); fetchReminders();
}, [fetchReminders]); }, [fetchReminders]);
let { folderUid } = router.query; // count the number of reminders group by reminder_date rounded at seconde
const remindersLength = useMemo(() => {
const remindersGroupByDate = reminders?.reduce((acc, reminder) => {
const reminderDate = new Date(reminder.reminder_date!).setSeconds(0, 0);
acc[reminderDate] = acc[reminderDate] ? acc[reminderDate] + 1 : 1;
return acc;
}, {} as Record<number, number>);
return Object.keys(remindersGroupByDate ?? {}).length;
}, [reminders]);
const doesCustomerHaveNotValidatedDocuments = useMemo(
() => customer.documents && customer.documents.some((document) => document.document_status !== EDocumentStatus.VALIDATED),
[customer.documents],
);
return ( return (
<div className={classes["root"]}> <div className={classes["root"]}>
@ -52,7 +68,7 @@ export default function EmailReminder(props: IProps) {
variant={EButtonVariant.PRIMARY} variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED} styletype={EButtonstyletype.OUTLINED}
fullwidth fullwidth
disabled={doesCustomerHaveDocument}> disabled={!doesCustomerHaveNotValidatedDocuments}>
Relancer par mail Relancer par mail
</Button> </Button>
)} )}
@ -68,21 +84,14 @@ export default function EmailReminder(props: IProps) {
<div className={classes["info"]}> <div className={classes["info"]}>
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}> <Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Dernière relance:{" "} Dernière relance:{" "}
{reminders && reminders.length > 0 ? new Date(reminders[0]!.reminder_date!).toLocaleDateString() : "-"} {reminders && remindersLength > 0 ? new Date(reminders[0]!.reminder_date!).toLocaleDateString() : "-"}
</Typography> </Typography>
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}> <Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Nombre de relance: {reminders && reminders.length > 0 ? reminders.length : "0"} Nombre de relance: {remindersLength}
</Typography> </Typography>
</div> </div>
</div> </div>
<ReminderModal <ReminderModal isOpen={isOpen} onRemindSuccess={fetchReminders} onClose={close} customer={customer} />
isOpen={isOpen}
onRemindSuccess={() => {
window.location.reload();
}}
onClose={close}
customer={customer}
/>
</div> </div>
); );
} }

View File

@ -1,4 +1,3 @@
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders"; import Folders from "@Front/Api/LeCoffreApi/Notary/Folders/Folders";
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button"; import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import Tabs from "@Front/Components/Elements/Tabs"; import Tabs from "@Front/Components/Elements/Tabs";
@ -7,16 +6,14 @@ import { DocumentIcon, UserPlusIcon } from "@heroicons/react/24/outline";
import Customer from "le-coffre-resources/dist/Customer"; import Customer from "le-coffre-resources/dist/Customer";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document"; import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import { OfficeFolder } from "le-coffre-resources/dist/Notary"; import { OfficeFolder } from "le-coffre-resources/dist/Notary";
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
import Link from "next/link"; import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { AnchorStatus } from ".."; import { AnchorStatus } from "..";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import ClientBox from "./ClientBox"; import ClientBox from "./ClientBox";
import DocumentTables from "./DocumentTables"; import DocumentTables from "./DocumentTables";
import EmailReminder from "./EmailReminder"; import EmailReminder from "./EmailReminder";
import NoDocument from "./NoDocument";
type IProps = { type IProps = {
folder: OfficeFolder; folder: OfficeFolder;
@ -27,7 +24,6 @@ export type ICustomer = Customer & { id: string };
export default function ClientView(props: IProps) { export default function ClientView(props: IProps) {
const { folder, anchorStatus } = props; const { folder, anchorStatus } = props;
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
const customers: ICustomer[] = useMemo( const customers: ICustomer[] = useMemo(
() => () =>
@ -60,8 +56,6 @@ export default function ClientView(props: IProps) {
[customers], [customers],
); );
const doesCustomerHaveDocument = useMemo(() => customer.documents && customer.documents.length > 0, [customer]);
const handleClientDelete = useCallback( const handleClientDelete = useCallback(
(customerUid: string) => { (customerUid: string) => {
if (!folder.uid) return; if (!folder.uid) return;
@ -77,12 +71,6 @@ export default function ClientView(props: IProps) {
[folder], [folder],
); );
useEffect(() => {
DocumentsNotary.getInstance()
.get({ where: { folder: { uid: folder.uid }, customer: { uid: customer.uid } }, include: { files: true } })
.then((documentsNotary) => setDocumentsNotary(documentsNotary));
}, [customer.uid, folder]);
return ( return (
<section className={classes["root"]}> <section className={classes["root"]}>
<div className={classes["tab-container"]}> <div className={classes["tab-container"]}>
@ -126,17 +114,12 @@ export default function ClientView(props: IProps) {
)} )}
<EmailReminder <EmailReminder
customer={customer} customer={customer}
doesCustomerHaveDocument={!doesCustomerHaveDocument}
isAnchored={anchorStatus !== AnchorStatus.NOT_ANCHORED} isAnchored={anchorStatus !== AnchorStatus.NOT_ANCHORED}
/> />
</div> </div>
</div> </div>
{doesCustomerHaveDocument || documentsNotary.length > 0 ? ( {customer.uid && folder.uid && <DocumentTables customerUid={customer.uid} folderUid={folder.uid} />}
<DocumentTables documents={customer.documents ?? []} documentsNotary={documentsNotary} folderUid={folder?.uid ?? ""} />
) : (
<NoDocument />
)}
</div> </div>
</section> </section>
); );

View File

@ -16,6 +16,7 @@ import { useRouter } from "next/router";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import classes from "./classes.module.scss"; import classes from "./classes.module.scss";
import { ToasterService } from "@Front/Components/DesignSystem/Toaster";
enum EClientSelection { enum EClientSelection {
ALL_CLIENTS = "all_clients", ALL_CLIENTS = "all_clients",
@ -68,7 +69,7 @@ export default function SendDocuments() {
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string), .modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
); );
setIsSending(false); setIsSending(false);
console.log("All files have been successfully sent."); ToasterService.getInstance().success({ title: "Succès !", description: "Votre document a été envoyée avec succès." });
} catch (error) { } catch (error) {
setIsSending(false); setIsSending(false);
console.error("Error while sending files: ", error); console.error("Error while sending files: ", error);

View File

@ -27,6 +27,7 @@ export default function Folder() {
.get({ .get({
q: { q: {
where: { status: EFolderStatus.LIVE }, where: { status: EFolderStatus.LIVE },
orderBy: { created_at: "desc" },
}, },
}) })
.then((folders) => { .then((folders) => {