This commit is contained in:
Vins 2024-09-09 05:45:00 +02:00
parent 62b51b4047
commit 5f7a4c2e63
10 changed files with 613 additions and 432 deletions

759
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"heroicons": "^2.1.5", "heroicons": "^2.1.5",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151", "le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.160",
"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

@ -87,4 +87,14 @@ export default class Customers extends BaseNotary {
return Promise.reject(err); return Promise.reject(err);
} }
} }
public async sendReminder(uid: string, documentsUid: string[]): Promise<void> {
const url = new URL(this.baseURl.concat(`/${uid}/send_reminder`));
try {
await this.postRequest<void>(url, { documentsUid });
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
} }

View File

@ -0,0 +1,41 @@
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetDocumentRemindersparams {
where?: {};
include?: {};
orderBy?: {};
}
// TODO Type getbyuid query params
export default class DocumentReminders extends BaseNotary {
private static instance: DocumentReminders;
private readonly baseURl = this.namespaceUrl.concat("/document_reminders");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
public async get(q: IGetDocumentRemindersparams): Promise<DocumentReminder[]> {
const url = new URL(this.baseURl);
const query = { q };
Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
try {
return await this.getRequest<DocumentReminder[]>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -0,0 +1,53 @@
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
import BaseNotary from "../BaseNotary";
// TODO Type get query params -> Where + inclue + orderby
export interface IGetDocumentNotaryparams {
where?: {};
include?: {};
orderBy?: {};
}
// TODO Type getbyuid query params
export default class DocumentsNotary extends BaseNotary {
private static instance: DocumentsNotary;
private readonly baseURl = this.namespaceUrl.concat("/documents_notary");
private constructor() {
super();
}
public static getInstance() {
if (!this.instance) {
return new this();
} else {
return this.instance;
}
}
// public async get(q: IGetDocumentNotaryparams): Promise<DocumentNotary[]> {
// const url = new URL(this.baseURl);
// const query = { q };
// Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
// try {
// return await this.getRequest<DocumentNotary[]>(url);
// } catch (err) {
// this.onError(err);
// return Promise.reject(err);
// }
// }
/**
* @description : Create a Document Notary
*/
public async post(body: any): Promise<DocumentNotary> {
const url = new URL(this.baseURl);
try {
return await this.postRequestFormData<DocumentNotary>(url, body);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
}
}

View File

@ -22,6 +22,8 @@ type IProps = {
defaultFiles?: IDocumentFileWithUid[]; defaultFiles?: IDocumentFileWithUid[];
onDelete?: (fileUid: string) => Promise<any>; onDelete?: (fileUid: string) => Promise<any>;
onAddFile?: (file: File) => Promise<any>; onAddFile?: (file: File) => Promise<any>;
onAddToList?: (file: IDocumentFile) => void;
onRemoveFromList?: (file: IDocumentFile) => void;
} & ( } & (
| { onDelete: (fileUid: string) => Promise<any>; onAddFile?: never; defaultFiles: IDocumentFileWithUid[] } | { onDelete: (fileUid: string) => Promise<any>; onAddFile?: never; defaultFiles: IDocumentFileWithUid[] }
| { onDelete?: never; onAddFile: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] } | { onDelete?: never; onAddFile: (file: File) => Promise<any>; defaultFiles: IDocumentFileWithUid[] }
@ -68,7 +70,7 @@ export type IDocumentFileWithUid = IDocumentFileBase & {
type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid; type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid;
export default function DragAndDrop(props: IProps) { export default function DragAndDrop(props: IProps) {
const { title, description, defaultFiles, onDelete, onAddFile } = props; const { title, description, defaultFiles, onDelete, onAddFile, onAddToList, onRemoveFromList } = props;
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]); const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]);
@ -95,6 +97,9 @@ export default function DragAndDrop(props: IProps) {
} }
return setTimeout(async () => { return setTimeout(async () => {
if (onAddToList) {
onAddToList(newDoc);
}
setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc))); setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc)));
}, 1000); }, 1000);
} catch (error: any) { } catch (error: any) {
@ -117,6 +122,9 @@ export default function DragAndDrop(props: IProps) {
const handleRemove = useCallback( const handleRemove = useCallback(
(documentFile: IDocumentFile) => { (documentFile: IDocumentFile) => {
if (onRemoveFromList) {
onRemoveFromList(documentFile);
}
const loadingDoc = { ...documentFile, isLoading: true }; const loadingDoc = { ...documentFile, isLoading: true };
setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === documentFile.id ? loadingDoc : doc))); setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === documentFile.id ? loadingDoc : doc)));

View File

@ -7,6 +7,7 @@ import Customer from "le-coffre-resources/dist/Customer";
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,6 +23,7 @@ export default function ReminderModal(props: IProps) {
const onRemind = useCallback(() => { const onRemind = useCallback(() => {
console.log("selectedOptions", selectedOptions); console.log("selectedOptions", selectedOptions);
Customers.getInstance().sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[]);
onRemindSuccess(); onRemindSuccess();
onClose?.(); onClose?.();
}, [onClose, onRemindSuccess, selectedOptions]); }, [onClose, onRemindSuccess, selectedOptions]);
@ -31,7 +33,7 @@ export default function ReminderModal(props: IProps) {
customer.documents?.map((document) => { customer.documents?.map((document) => {
return { return {
label: document.document_type?.name ?? "", label: document.document_type?.name ?? "",
value: document.document_type?.uid ?? "", value: document.uid ?? "",
}; };
}) ?? [], }) ?? [],
[customer], [customer],

View File

@ -10,6 +10,9 @@ import ReminderModal from "./ReminderModal";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Module from "@Front/Config/Module"; import Module from "@Front/Config/Module";
import Link from "next/link"; import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import DocumentReminders from "@Front/Api/LeCoffreApi/Notary/DocumentReminders/DocumentReminders";
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
type IProps = { type IProps = {
customer: Customer; customer: Customer;
@ -19,9 +22,25 @@ type IProps = {
export default function EmailReminder(props: IProps) { export default function EmailReminder(props: IProps) {
const { customer, doesCustomerHaveDocument, isAnchored } = props; const { customer, doesCustomerHaveDocument, isAnchored } = props;
const [reminders, setReminders] = useState<DocumentReminder[] | null>(null);
const { isOpen, open, close } = useOpenable(); const { isOpen, open, close } = useOpenable();
const router = useRouter(); const router = useRouter();
const fetchReminders = useCallback(async () => {
DocumentReminders.getInstance()
.get({
where: { document: { depositor: { uid: customer.uid } } },
include: { document: "true" },
orderBy: { reminder_date: "desc" },
})
.then((reminders) => setReminders(reminders))
.catch((e) => console.warn(e));
}, [customer.uid]);
useEffect(() => {
fetchReminders();
}, []);
let { folderUid } = router.query; let { folderUid } = router.query;
return ( return (
@ -47,16 +66,38 @@ export default function EmailReminder(props: IProps) {
</Link> </Link>
<div className={classes["info"]}> <div className={classes["info"]}>
{/* TODO: mettre la date de la dernière relance */} {/* TODO: mettre la date de la dernière relance */}
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}> {!reminders && (
Dernière relance: - <Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
</Typography> Dernière relance: -
</Typography>
)}
{reminders && reminders.length > 0 && (
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Dernière relance: {new Date(reminders[0]!.reminder_date!).toLocaleDateString()}
</Typography>
)}
{/* TODO: mettre le nombre de relance */} {/* TODO: mettre le nombre de relance */}
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}> {!reminders && (
Nombre de relance: - <Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
</Typography> Nombre de relance: -
</Typography>
)}
{reminders && reminders.length > 0 && (
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Nombre de relance: {reminders.length}
</Typography>
)}
</div> </div>
</div> </div>
<ReminderModal isOpen={isOpen} onRemindSuccess={() => {}} onClose={close} customer={customer} /> <ReminderModal
isOpen={isOpen}
onRemindSuccess={() => {
window.location.reload();
}}
onClose={close}
customer={customer}
/>
</div> </div>
); );
} }

View File

@ -15,17 +15,34 @@ 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 DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
enum EClientSelection { enum EClientSelection {
ALL_CLIENTS = "all_clients", ALL_CLIENTS = "all_clients",
SELECTED_CLIENTS = "selected_clients", SELECTED_CLIENTS = "selected_clients",
} }
type IDocumentFileBase = {
id: string;
file: File | null;
uid?: string;
isLoading?: boolean;
error?: string;
};
export type IDocumentFileWithUid = IDocumentFileBase & {
uid: string;
};
type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid;
export default function SendDocuments() { export default function SendDocuments() {
const router = useRouter(); const router = useRouter();
let { folderUid } = router.query; let { folderUid } = router.query;
const [folder, setFolder] = useState<OfficeFolder | null>(null); const [folder, setFolder] = useState<OfficeFolder | null>(null);
const [clientSelection, setClientSelection] = useState<EClientSelection | null>(null); const [clientSelection, setClientSelection] = useState<EClientSelection | null>(EClientSelection.SELECTED_CLIENTS);
const [files, setFiles] = useState<IDocumentFile[]>([]);
const [selectedClients, setSelectedClients] = useState<string[]>([]);
const onFormSubmit = useCallback( const onFormSubmit = useCallback(
async ( async (
@ -34,10 +51,53 @@ export default function SendDocuments() {
[key: string]: any; [key: string]: any;
}, },
//TODO: when back is done //TODO: when back is done
) => {}, ) => {
[], if (folder?.customers && clientSelection === EClientSelection.ALL_CLIENTS) {
const allClientIds = folder.customers.map((customer) => customer.uid ?? "");
setSelectedClients(allClientIds);
}
const formData = new FormData();
selectedClients.forEach(async (customer) => {
console.log(files[0]?.file);
if (!files[0]?.file) return;
formData.append("customerUid", customer as string);
formData.append("folderUid", folderUid as string);
formData.append("file", files[0].file as File);
const documentNotary = await DocumentsNotary.getInstance().post(formData);
console.log(documentNotary);
});
// const formData = new FormData();
// files.forEach((file) => {
// if (file.file) {
// formData.append("file", file.file);
// }
// });
// selectedClients.forEach((client) => {
// formData.append("customers", client);
// });
// formData.append("folder", folderUid as string);
// const documentNotary = await DocumentsNotary.getInstance().post(formData);
// console.log(documentNotary);
},
[files, clientSelection, selectedClients],
); );
const onAddToList = useCallback((documentFile: IDocumentFile) => {
const test = files;
test.push(documentFile);
setFiles(test);
}, []);
const onRemoveFromList = useCallback((documentFile: IDocumentFile) => {
const test = files;
const index = test.findIndex((doc) => doc.id === documentFile.id);
if (index === -1) return;
test.splice(index, 1);
setFiles(test);
}, []);
const fetchFolder = useCallback(async () => { const fetchFolder = useCallback(async () => {
Folders.getInstance() Folders.getInstance()
.getByUid(folderUid as string, { .getByUid(folderUid as string, {
@ -53,14 +113,34 @@ export default function SendDocuments() {
.catch((e) => console.warn(e)); .catch((e) => console.warn(e));
}, [folderUid]); }, [folderUid]);
const onClientSelectionChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const onClientSelectionChange = useCallback(
setClientSelection(e.target.value as EClientSelection); (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value); const selection = e.target.value as EClientSelection;
setClientSelection(selection);
if (selection === EClientSelection.ALL_CLIENTS && folder?.customers) {
// Automatically select all customers
const allClientIds = folder.customers.map((customer) => customer.uid ?? "");
setSelectedClients(allClientIds);
} else {
// Clear selected clients when selecting "Sélectionner certains clients"
setSelectedClients([]);
}
},
[folder],
);
const handleClientSelectionChange = useCallback((selectedOptions: any) => {
setSelectedClients(selectedOptions.map((option: any) => option.id));
}, []); }, []);
useEffect(() => { useEffect(() => {
fetchFolder(); fetchFolder();
}, [fetchFolder]); if (folder?.customers && clientSelection === EClientSelection.ALL_CLIENTS) {
const allClientIds = folder.customers.map((customer) => customer.uid ?? "");
setSelectedClients(allClientIds);
}
}, []);
const backUrl = useMemo( const backUrl = useMemo(
() => () =>
@ -100,16 +180,26 @@ export default function SendDocuments() {
value={EClientSelection.SELECTED_CLIENTS} value={EClientSelection.SELECTED_CLIENTS}
label="Sélectionner certains clients" label="Sélectionner certains clients"
onChange={onClientSelectionChange} onChange={onClientSelectionChange}
defaultChecked
/> />
</div> </div>
<Form onSubmit={onFormSubmit} className={classes["form"]}> <Form onSubmit={onFormSubmit} className={classes["form"]}>
{clientSelection === EClientSelection.SELECTED_CLIENTS && ( {clientSelection === EClientSelection.SELECTED_CLIENTS && (
<AutocompleteMultiSelect label="Choisir le ou les clients: " options={clientsOptions} /> <AutocompleteMultiSelect
label="Choisir le ou les clients: "
options={clientsOptions}
onSelectionChange={handleClientSelectionChange}
/>
)} )}
{clientSelection && ( {clientSelection && (
<> <>
<DragAndDrop title="Glisser ou déposer ou" description="Formats acceptés : PDF, JPG Taille maximale : 5 Mo" /> <DragAndDrop
title="Glisser ou déposer ou"
description="Formats acceptés : PDF, JPG Taille maximale : 5 Mo"
onAddToList={onAddToList}
onRemoveFromList={onRemoveFromList}
/>
<div className={classes["buttons-container"]}> <div className={classes["buttons-container"]}>
<a href={backUrl}> <a href={backUrl}>

View File

@ -80,7 +80,8 @@
"skipLibCheck": true, "skipLibCheck": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"allowJs": true, "allowJs": true,
"isolatedModules": true "isolatedModules": true,
"downlevelIteration": true
}, },
"include": [ "include": [
"next-env.d.ts", "next-env.d.ts",