t pMerge branch 'dev' into staging
This commit is contained in:
commit
28b37aea62
759
package-lock.json
generated
759
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||||
|
|
||||||
|
import BaseNotary from "../BaseNotary";
|
||||||
|
|
||||||
|
export interface IGetDocumentNotaryparams {
|
||||||
|
where?: {};
|
||||||
|
include?: {};
|
||||||
|
orderBy?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Delete a Document Notary
|
||||||
|
*/
|
||||||
|
public async delete(uid: string): Promise<void> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
url.pathname = url.pathname.concat(`/${uid}`);
|
||||||
|
try {
|
||||||
|
await this.deleteRequest(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/front/Api/LeCoffreApi/Notary/FilesNotary/Files.ts
Normal file
71
src/front/Api/LeCoffreApi/Notary/FilesNotary/Files.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { File } from "le-coffre-resources/dist/SuperAdmin";
|
||||||
|
|
||||||
|
import BaseNotary from "../BaseNotary";
|
||||||
|
|
||||||
|
export interface IGetFilesparams {
|
||||||
|
where?: {};
|
||||||
|
include?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IPutFilesParams = {};
|
||||||
|
|
||||||
|
export interface IPostFilesParams {}
|
||||||
|
|
||||||
|
export default class FilesNotary extends BaseNotary {
|
||||||
|
private static instance: FilesNotary;
|
||||||
|
private readonly baseURl = this.namespaceUrl.concat("/files-notary");
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance() {
|
||||||
|
return (this.instance ??= new this());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(q: IGetFilesparams): Promise<File[]> {
|
||||||
|
const url = new URL(this.baseURl);
|
||||||
|
const query = { q };
|
||||||
|
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||||
|
try {
|
||||||
|
const files = await this.getRequest<File[]>(url);
|
||||||
|
return files;
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getByUid(uid: string, q?: any): Promise<File> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||||
|
const query = { q };
|
||||||
|
if (q) Object.entries(query).forEach(([key, value]) => url.searchParams.set(key, JSON.stringify(value)));
|
||||||
|
try {
|
||||||
|
const file = await this.getRequest<File>(url);
|
||||||
|
return file;
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(uid: string): Promise<File> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/${uid}`));
|
||||||
|
try {
|
||||||
|
return await this.deleteRequest<File>(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async download(uid: string): Promise<any> {
|
||||||
|
const url = new URL(this.baseURl.concat(`/download/${uid}`));
|
||||||
|
try {
|
||||||
|
return await this.getRequest<any>(url);
|
||||||
|
} catch (err) {
|
||||||
|
this.onError(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,6 @@ export default function Autocomplete(props: IProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchValue) {
|
if (searchValue) {
|
||||||
const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase()));
|
const filteredOptions = options.filter((option) => getLabel(option)?.toLowerCase().includes(searchValue.toLowerCase()));
|
||||||
console.log(filteredOptions);
|
|
||||||
if (filteredOptions.length === 0)
|
if (filteredOptions.length === 0)
|
||||||
return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]);
|
return setFilteredOptions([{ id: "no-results", label: "Aucun résulats", notSelectable: true }]);
|
||||||
return setFilteredOptions(filteredOptions);
|
return setFilteredOptions(filteredOptions);
|
||||||
|
@ -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>;
|
||||||
|
name?: string;
|
||||||
|
onChange?: (files: File[]) => 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, name, onChange } = props;
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]);
|
const [documentFiles, setDocumentFiles] = useState<IDocumentFile[]>([]);
|
||||||
@ -79,6 +81,8 @@ export default function DragAndDrop(props: IProps) {
|
|||||||
}
|
}
|
||||||
}, [defaultFiles]);
|
}, [defaultFiles]);
|
||||||
|
|
||||||
|
useEffect(() => onChange?.(documentFiles.map((doc) => doc.file).filter((file) => file !== null) as File[]), [documentFiles, onChange]);
|
||||||
|
|
||||||
const handleAddFiles = useCallback(
|
const handleAddFiles = useCallback(
|
||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
@ -202,6 +206,7 @@ export default function DragAndDrop(props: IProps) {
|
|||||||
function inputFile() {
|
function inputFile() {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
|
name={name}
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
|
@ -15,10 +15,11 @@ type IProps = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onSelectionChange?: (option: IOption) => void;
|
onSelectionChange?: (option: IOption) => void;
|
||||||
selectedOption?: IOption | null;
|
selectedOption?: IOption | null;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Dropdown(props: IProps) {
|
export default function Dropdown(props: IProps) {
|
||||||
const { options, placeholder, disabled, onSelectionChange, selectedOption: selectedOptionProps, label } = props;
|
const { options, placeholder, disabled, onSelectionChange, selectedOption: selectedOptionProps, label, className } = props;
|
||||||
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
|
const [selectedOption, setSelectedOption] = useState<IOption | null>(selectedOptionProps ?? null);
|
||||||
const openable = useOpenable({ defaultOpen: false });
|
const openable = useOpenable({ defaultOpen: false });
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ export default function Dropdown(props: IProps) {
|
|||||||
openable={openable}
|
openable={openable}
|
||||||
onSelect={handleOnSelect}
|
onSelect={handleOnSelect}
|
||||||
selectedOptions={selectedOption ? [selectedOption] : []}>
|
selectedOptions={selectedOption ? [selectedOption] : []}>
|
||||||
<div className={classes["root"]}>
|
<div className={classNames(classes["root"], className)}>
|
||||||
{label && (
|
{label && (
|
||||||
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
<Typography className={classes["label"]} typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.CONTRAST_DEFAULT}>
|
||||||
{label}
|
{label}
|
||||||
|
@ -39,7 +39,6 @@ class ToastElementClass extends React.Component<IPropsClass, IState> {
|
|||||||
|
|
||||||
public override render(): JSX.Element {
|
public override render(): JSX.Element {
|
||||||
const toast = this.props.toast;
|
const toast = this.props.toast;
|
||||||
console.log(toast);
|
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
"--data-duration": `${toast.time}ms`,
|
"--data-duration": `${toast.time}ms`,
|
||||||
|
@ -80,7 +80,6 @@ export default function Tabs<T>({ onSelect, tabs: propsTabs }: IProps<T>) {
|
|||||||
newTabs.splice(index, 1);
|
newTabs.splice(index, 1);
|
||||||
newTabs.unshift(tabs.current[index]!);
|
newTabs.unshift(tabs.current[index]!);
|
||||||
tabs.current = newTabs;
|
tabs.current = newTabs;
|
||||||
console.log("Updated values ; ", tabs.current);
|
|
||||||
handleSelect(value);
|
handleSelect(value);
|
||||||
},
|
},
|
||||||
[handleSelect],
|
[handleSelect],
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import React from "react";
|
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||||
|
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
|
||||||
import classes from "./classes.module.scss";
|
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
import { useRouter } from "next/router";
|
import IconButton from "@Front/Components/DesignSystem/IconButton";
|
||||||
|
import Table from "@Front/Components/DesignSystem/Table";
|
||||||
|
import { IHead, IRowProps } from "@Front/Components/DesignSystem/Table/MuiTable";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
import JwtService from "@Front/Services/JwtService/JwtService";
|
||||||
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||||
import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import { saveAs } from "file-saver";
|
||||||
import { IHead } from "@Front/Components/DesignSystem/Table/MuiTable";
|
import JSZip from "jszip";
|
||||||
import Table from "@Front/Components/DesignSystem/Table";
|
import { DocumentNotary } from "le-coffre-resources/dist/Notary";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
type IProps = {};
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
const header: readonly IHead[] = [
|
const header: readonly IHead[] = [
|
||||||
{
|
{
|
||||||
@ -29,9 +33,60 @@ const header: readonly IHead[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function ReceivedDocuments(props: IProps) {
|
export default function ReceivedDocuments() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
let { folderUid } = router.query;
|
let { folderUid } = router.query;
|
||||||
|
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const customerUid = JwtService.getInstance().decodeCustomerJwt()?.customerId;
|
||||||
|
if (!folderUid || !customerUid) return;
|
||||||
|
DocumentsNotary.getInstance()
|
||||||
|
.get({ where: { folder: { uid: folderUid }, customer: { uid: customerUid } }, include: { files: true } })
|
||||||
|
.then((documentsNotary) => setDocumentsNotary(documentsNotary));
|
||||||
|
}, [folderUid]);
|
||||||
|
|
||||||
|
const onDownload = useCallback((doc: DocumentNotary) => {
|
||||||
|
const file = doc.files?.[0];
|
||||||
|
if (!file || !file?.uid) return;
|
||||||
|
|
||||||
|
return FilesNotary.getInstance()
|
||||||
|
.download(file.uid)
|
||||||
|
.then((blob) => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = file.file_name ?? "file";
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
})
|
||||||
|
.catch((e) => console.warn(e));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onDownloadAll = useCallback(async () => {
|
||||||
|
if (documentsNotary.length === 0) return;
|
||||||
|
|
||||||
|
const zip = new JSZip();
|
||||||
|
const folder = zip.folder("documents") || zip;
|
||||||
|
|
||||||
|
const downloadPromises = documentsNotary.map(async (doc) => {
|
||||||
|
const file = doc.files?.[0];
|
||||||
|
if (file && file.uid) {
|
||||||
|
const blob = await FilesNotary.getInstance().download(file.uid);
|
||||||
|
folder.file(file.file_name ?? "file", blob);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(downloadPromises);
|
||||||
|
|
||||||
|
zip.generateAsync({ type: "blob" })
|
||||||
|
.then((blob: any) => {
|
||||||
|
saveAs(blob, "documents.zip");
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.error("Error generating ZIP file: ", error);
|
||||||
|
});
|
||||||
|
}, [documentsNotary]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultTemplate title={"Documents reçus"} isPadding={false}>
|
<DefaultTemplate title={"Documents reçus"} isPadding={false}>
|
||||||
@ -45,15 +100,25 @@ export default function ReceivedDocuments(props: IProps) {
|
|||||||
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
Un document vous a été envoyé
|
Un document vous a été envoyé
|
||||||
</Typography>
|
</Typography>
|
||||||
<Table className={classes["table"]} header={header} rows={[{ key: "1", name: "todo", sentAt: "todo", actions: "todo" }]} />
|
<Table className={classes["table"]} header={header} rows={buildRows(documentsNotary, onDownload)} />
|
||||||
<Button
|
<Button
|
||||||
variant={EButtonVariant.PRIMARY}
|
variant={EButtonVariant.PRIMARY}
|
||||||
size={EButtonSize.LG}
|
size={EButtonSize.LG}
|
||||||
styletype={EButtonstyletype.CONTAINED}
|
styletype={EButtonstyletype.CONTAINED}
|
||||||
leftIcon={<ArrowDownTrayIcon />}>
|
leftIcon={<ArrowDownTrayIcon />}
|
||||||
|
onClick={onDownloadAll}>
|
||||||
Tout télécharger
|
Tout télécharger
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</DefaultTemplate>
|
</DefaultTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildRows(documentsNotary: DocumentNotary[], onDownloadFileNotary: (doc: DocumentNotary) => void): IRowProps[] {
|
||||||
|
return documentsNotary.map((documentNotary) => ({
|
||||||
|
key: documentNotary.uid ?? "",
|
||||||
|
name: documentNotary.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||||
|
sentAt: new Date(documentNotary.created_at!).toLocaleDateString(),
|
||||||
|
actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customer-filter{
|
||||||
|
width: 472px;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $screen-m) {
|
@media screen and (max-width: $screen-m) {
|
||||||
padding: var(--spacing-3);
|
padding: var(--spacing-3);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
|
import Customers from "@Front/Api/LeCoffreApi/Notary/Customers/Customers";
|
||||||
|
import DocumentReminders from "@Front/Api/LeCoffreApi/Notary/DocumentReminders/DocumentReminders";
|
||||||
|
import Dropdown from "@Front/Components/DesignSystem/Dropdown";
|
||||||
|
import { IOption } from "@Front/Components/DesignSystem/Dropdown/DropdownMenu/DropdownOption";
|
||||||
import Table from "@Front/Components/DesignSystem/Table";
|
import Table from "@Front/Components/DesignSystem/Table";
|
||||||
import { IHead } from "@Front/Components/DesignSystem/Table/MuiTable";
|
import { IHead, IRowProps } from "@Front/Components/DesignSystem/Table/MuiTable";
|
||||||
|
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
import BackArrow from "@Front/Components/Elements/BackArrow";
|
import BackArrow from "@Front/Components/Elements/BackArrow";
|
||||||
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
|
||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
|
import Customer from "le-coffre-resources/dist/Customer";
|
||||||
|
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||||||
|
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
|
|
||||||
@ -31,9 +39,76 @@ const header: readonly IHead[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function DocumentsReminderHistory(props: IProps) {
|
export default function DocumentsReminderHistory(props: IProps) {
|
||||||
|
const [reminders, setReminders] = useState<DocumentReminder[] | null>(null);
|
||||||
|
const [customers, setCustomers] = useState<Customer[] | null>(null);
|
||||||
|
const [customerOption, setCustomerOption] = useState<IOption | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
let { folderUid } = router.query;
|
let { folderUid } = router.query;
|
||||||
|
|
||||||
|
const fetchReminders = useCallback(() => {
|
||||||
|
DocumentReminders.getInstance()
|
||||||
|
.get({
|
||||||
|
...(customerOption && customerOption.id !== "tous" && { where: { document: { depositor: { uid: customerOption.id } } } }),
|
||||||
|
include: {
|
||||||
|
document: {
|
||||||
|
include: {
|
||||||
|
depositor: {
|
||||||
|
include: {
|
||||||
|
contact: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
document_type: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { reminder_date: "desc" },
|
||||||
|
})
|
||||||
|
.then((reminders) => setReminders(reminders))
|
||||||
|
.catch((e) => console.warn(e));
|
||||||
|
}, [customerOption]);
|
||||||
|
|
||||||
|
const fetchCustomers = useCallback(async () => {
|
||||||
|
if (!folderUid) return;
|
||||||
|
Customers.getInstance()
|
||||||
|
.get({
|
||||||
|
where: {
|
||||||
|
office_folders: {
|
||||||
|
some: {
|
||||||
|
uid: folderUid as string,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(setCustomers)
|
||||||
|
.catch(console.warn);
|
||||||
|
}, [folderUid]);
|
||||||
|
|
||||||
|
const customersOptions: IOption[] = useMemo(() => {
|
||||||
|
let options = [
|
||||||
|
{
|
||||||
|
id: "tous",
|
||||||
|
label: "Tous",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
customers?.forEach((customer) => {
|
||||||
|
options.push({
|
||||||
|
id: customer.uid ?? "",
|
||||||
|
label: `${customer.contact?.first_name} ${customer.contact?.last_name}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}, [customers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchReminders();
|
||||||
|
fetchCustomers();
|
||||||
|
}, [customerOption, customersOptions, fetchCustomers, fetchReminders]);
|
||||||
|
|
||||||
|
const onSelectionChange = useCallback((option: IOption | null) => {
|
||||||
|
setCustomerOption(option ?? null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultTemplate title={"Historique des relances de documents"} isPadding={false}>
|
<DefaultTemplate title={"Historique des relances de documents"} isPadding={false}>
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
@ -46,8 +121,40 @@ export default function DocumentsReminderHistory(props: IProps) {
|
|||||||
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
|
||||||
Historique des relances de documents
|
Historique des relances de documents
|
||||||
</Typography>
|
</Typography>
|
||||||
<Table className={classes["table"]} header={header} rows={[{ key: "1", name: "todo", sentAt: "todo", actions: "todo" }]} />
|
<Dropdown
|
||||||
|
className={classes["customer-filter"]}
|
||||||
|
options={customersOptions}
|
||||||
|
onSelectionChange={onSelectionChange}
|
||||||
|
selectedOption={customerOption ?? customersOptions?.[0]}
|
||||||
|
/>
|
||||||
|
<Table className={classes["table"]} header={header} rows={buildRows(reminders)} />
|
||||||
</div>
|
</div>
|
||||||
</DefaultTemplate>
|
</DefaultTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildRows(reminders: DocumentReminder[] | null): IRowProps[] {
|
||||||
|
if (!reminders) return [];
|
||||||
|
return reminders.map((reminder) => ({
|
||||||
|
key: reminder.uid ?? "",
|
||||||
|
remindedAt: new Date(reminder.reminder_date!).toLocaleDateString(),
|
||||||
|
customer: `${reminder.document?.depositor?.contact?.first_name} ${reminder.document?.depositor?.contact?.last_name}`,
|
||||||
|
document_type: reminder.document?.document_type?.name,
|
||||||
|
statut: getTag(reminder.document?.document_status as EDocumentStatus),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTag(status: EDocumentStatus) {
|
||||||
|
switch (status) {
|
||||||
|
case EDocumentStatus.ASKED:
|
||||||
|
return <Tag label={status} color={ETagColor.INFO} />;
|
||||||
|
case EDocumentStatus.DEPOSITED:
|
||||||
|
return <Tag label={status} color={ETagColor.WARNING} />;
|
||||||
|
case EDocumentStatus.VALIDATED:
|
||||||
|
return <Tag label={status} color={ETagColor.SUCCESS} />;
|
||||||
|
case EDocumentStatus.REFUSED:
|
||||||
|
return <Tag label={status} color={ETagColor.ERROR} />;
|
||||||
|
default:
|
||||||
|
return <Tag label={status} color={ETagColor.INFO} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import Documents from "@Front/Api/LeCoffreApi/Notary/Documents/Documents";
|
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
|
||||||
import Modal from "@Front/Components/DesignSystem/Modal";
|
import Modal from "@Front/Components/DesignSystem/Modal";
|
||||||
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";
|
||||||
@ -15,7 +15,7 @@ export default function DeleteSentDocumentModal(props: IProps) {
|
|||||||
|
|
||||||
const onDelete = useCallback(
|
const onDelete = useCallback(
|
||||||
() =>
|
() =>
|
||||||
Documents.getInstance()
|
DocumentsNotary.getInstance()
|
||||||
.delete(documentUid)
|
.delete(documentUid)
|
||||||
.then(() => onDeleteSuccess(documentUid))
|
.then(() => onDeleteSuccess(documentUid))
|
||||||
.then(onClose)
|
.then(onClose)
|
||||||
|
@ -8,18 +8,21 @@ import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Ty
|
|||||||
import Module from "@Front/Config/Module";
|
import Module from "@Front/Config/Module";
|
||||||
import useOpenable from "@Front/Hooks/useOpenable";
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import { ArrowDownTrayIcon, EyeIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
|
import { useMediaQuery } from "@mui/material";
|
||||||
import { Document } from "le-coffre-resources/dist/Customer";
|
import { Document } 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 { 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 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 { useMediaQuery } from "@mui/material";
|
import FilesNotary from "@Front/Api/LeCoffreApi/Notary/FilesNotary/Files";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
documents: Document[];
|
documents: Document[];
|
||||||
|
documentsNotary: DocumentNotary[];
|
||||||
folderUid: string;
|
folderUid: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,7 +34,7 @@ const tradDocumentStatus: Record<EDocumentStatus, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function DocumentTables(props: IProps) {
|
export default function DocumentTables(props: IProps) {
|
||||||
const { documents: documentsProps, folderUid } = props;
|
const { documents: documentsProps, folderUid, documentsNotary } = props;
|
||||||
const [documents, setDocuments] = useState<Document[]>(documentsProps);
|
const [documents, setDocuments] = useState<Document[]>(documentsProps);
|
||||||
const [documentUid, setDocumentUid] = useState<string | null>(null);
|
const [documentUid, setDocumentUid] = useState<string | null>(null);
|
||||||
|
|
||||||
@ -79,6 +82,23 @@ export default function DocumentTables(props: IProps) {
|
|||||||
.catch((e) => console.warn(e));
|
.catch((e) => console.warn(e));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onDownloadFileNotary = useCallback((doc: DocumentNotary) => {
|
||||||
|
const file = doc.files?.[0];
|
||||||
|
if (!file || !file?.uid) return;
|
||||||
|
|
||||||
|
return FilesNotary.getInstance()
|
||||||
|
.download(file.uid)
|
||||||
|
.then((blob) => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = file.file_name ?? "file";
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
})
|
||||||
|
.catch((e) => console.warn(e));
|
||||||
|
}, []);
|
||||||
|
|
||||||
const askedDocuments: IRowProps[] = useMemo(
|
const askedDocuments: IRowProps[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
documents
|
documents
|
||||||
@ -190,27 +210,25 @@ export default function DocumentTables(props: IProps) {
|
|||||||
[documents],
|
[documents],
|
||||||
);
|
);
|
||||||
|
|
||||||
//TODO: modify accordingly when the back will handle sent documents
|
|
||||||
const sentDocuments: IRowProps[] = useMemo(
|
const sentDocuments: IRowProps[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
documents
|
documentsNotary
|
||||||
.map((document) => {
|
.map((document) => {
|
||||||
if (document.document_status !== "sent") return null;
|
|
||||||
return {
|
return {
|
||||||
key: document.uid,
|
key: document.uid,
|
||||||
document_type: document.document_type?.name ?? "_",
|
document_type: document.files?.[0]?.file_name?.split(".")?.[0] ?? "_",
|
||||||
document_status: <Tag color={ETagColor.ERROR} variant={ETagVariant.SEMI_BOLD} label={"Envoyé"} />,
|
document_status: <Tag color={ETagColor.NEUTRAL} variant={ETagVariant.SEMI_BOLD} label={"ENVOYÉ"} />,
|
||||||
date: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
date: document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_",
|
||||||
actions: (
|
actions: (
|
||||||
<div className={classes["actions"]}>
|
<div className={classes["actions"]}>
|
||||||
<IconButton onClick={() => onDownload(document)} icon={<ArrowDownTrayIcon />} />
|
<IconButton onClick={() => onDownloadFileNotary(document)} icon={<ArrowDownTrayIcon />} />
|
||||||
<IconButton icon={<TrashIcon onClick={() => openDeleteSentDocumentModal(document.uid)} />} />
|
<IconButton icon={<TrashIcon onClick={() => openDeleteSentDocumentModal(document.uid)} />} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((document) => document !== null) as IRowProps[],
|
.filter((document) => document !== null) as IRowProps[],
|
||||||
[documents, onDownload, openDeleteSentDocumentModal],
|
[documentsNotary, onDownloadFileNotary, openDeleteSentDocumentModal],
|
||||||
);
|
);
|
||||||
|
|
||||||
const progress = useMemo(() => {
|
const progress = useMemo(() => {
|
||||||
@ -219,13 +237,9 @@ 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(
|
const handleDelete = useCallback((_documentUid: string) => {
|
||||||
(documentUid: string) => {
|
|
||||||
setDocuments(documents.filter((document) => document.uid !== documentUid));
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
},
|
}, []);
|
||||||
[documents],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
|
@ -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;
|
||||||
@ -21,17 +22,17 @@ export default function ReminderModal(props: IProps) {
|
|||||||
const [isAllSelected, setIsAllSelected] = useState(false);
|
const [isAllSelected, setIsAllSelected] = useState(false);
|
||||||
|
|
||||||
const onRemind = useCallback(() => {
|
const onRemind = useCallback(() => {
|
||||||
console.log("selectedOptions", selectedOptions);
|
Customers.getInstance().sendReminder(customer.uid!, selectedOptions.map((option) => option.value) as string[]);
|
||||||
onRemindSuccess();
|
onRemindSuccess();
|
||||||
onClose?.();
|
onClose?.();
|
||||||
}, [onClose, onRemindSuccess, selectedOptions]);
|
}, [customer.uid, onClose, onRemindSuccess, selectedOptions]);
|
||||||
|
|
||||||
const documentsOptions: IOption[] = useMemo(
|
const documentsOptions: IOption[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
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],
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
|
import DocumentReminders from "@Front/Api/LeCoffreApi/Notary/DocumentReminders/DocumentReminders";
|
||||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
import IconButton, { EIconButtonVariant } from "@Front/Components/DesignSystem/IconButton";
|
||||||
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Module from "@Front/Config/Module";
|
||||||
import useOpenable from "@Front/Hooks/useOpenable";
|
import useOpenable from "@Front/Hooks/useOpenable";
|
||||||
import { ClockIcon, EnvelopeIcon } from "@heroicons/react/24/outline";
|
import { ClockIcon, EnvelopeIcon } from "@heroicons/react/24/outline";
|
||||||
import Customer from "le-coffre-resources/dist/Customer";
|
import Customer from "le-coffre-resources/dist/Customer";
|
||||||
|
import { DocumentReminder } from "le-coffre-resources/dist/Notary";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
import ReminderModal from "./ReminderModal";
|
import ReminderModal from "./ReminderModal";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import Module from "@Front/Config/Module";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
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();
|
||||||
|
}, [fetchReminders]);
|
||||||
|
|
||||||
let { folderUid } = router.query;
|
let { folderUid } = router.query;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -42,21 +61,28 @@ export default function EmailReminder(props: IProps) {
|
|||||||
title={"Voir l'historique des relances"}
|
title={"Voir l'historique des relances"}
|
||||||
href={Module.getInstance()
|
href={Module.getInstance()
|
||||||
.get()
|
.get()
|
||||||
.modules.pages.Folder.pages.DocumentsReminderHistory.props.path.replace("[folderUid]", folderUid as string)}>
|
.modules.pages.Folder.pages.DocumentsReminderHistory.props.path.replace("[folderUid]", folderUid as string)
|
||||||
|
.replace("[customerUid]", customer.uid ?? "")}>
|
||||||
<IconButton icon={<ClockIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
<IconButton icon={<ClockIcon />} variant={EIconButtonVariant.NEUTRAL} />
|
||||||
</Link>
|
</Link>
|
||||||
<div className={classes["info"]}>
|
<div className={classes["info"]}>
|
||||||
{/* TODO: mettre la date de la dernière relance */}
|
|
||||||
<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() : "-"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{/* TODO: mettre le nombre de relance */}
|
|
||||||
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
<Typography typo={ETypo.TEXT_XS_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
|
||||||
Nombre de relance: -
|
Nombre de relance: {reminders && reminders.length > 0 ? reminders.length : "0"}
|
||||||
</Typography>
|
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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";
|
||||||
@ -6,8 +7,9 @@ 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, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { AnchorStatus } from "..";
|
import { AnchorStatus } from "..";
|
||||||
import classes from "./classes.module.scss";
|
import classes from "./classes.module.scss";
|
||||||
@ -25,6 +27,7 @@ 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(
|
||||||
() =>
|
() =>
|
||||||
@ -74,6 +77,12 @@ 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"]}>
|
||||||
@ -123,8 +132,8 @@ export default function ClientView(props: IProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{doesCustomerHaveDocument ? (
|
{doesCustomerHaveDocument || documentsNotary.length > 0 ? (
|
||||||
<DocumentTables documents={customer.documents ?? []} folderUid={folder?.uid ?? ""} />
|
<DocumentTables documents={customer.documents ?? []} documentsNotary={documentsNotary} folderUid={folder?.uid ?? ""} />
|
||||||
) : (
|
) : (
|
||||||
<NoDocument />
|
<NoDocument />
|
||||||
)}
|
)}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import backgroundImage from "@Assets/images/background_refonte.svg";
|
import backgroundImage from "@Assets/images/background_refonte.svg";
|
||||||
|
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 AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
|
import AutocompleteMultiSelect from "@Front/Components/DesignSystem/AutocompleteMultiSelect";
|
||||||
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
@ -26,6 +27,8 @@ export default function SendDocuments() {
|
|||||||
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>(null);
|
||||||
|
const [selectedClients, setSelectedClients] = useState<string[]>([]);
|
||||||
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
|
|
||||||
const onFormSubmit = useCallback(
|
const onFormSubmit = useCallback(
|
||||||
async (
|
async (
|
||||||
@ -33,9 +36,41 @@ export default function SendDocuments() {
|
|||||||
_values: {
|
_values: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
},
|
},
|
||||||
//TODO: when back is done
|
) => {
|
||||||
) => {},
|
if (!files.length) {
|
||||||
[],
|
console.error("No files to send");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
selectedClients.map(async (customer) => {
|
||||||
|
const promises = files.map(async (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("customerUid", customer as string);
|
||||||
|
formData.append("folderUid", folderUid as string);
|
||||||
|
formData.append("name", file.name);
|
||||||
|
formData.append("file", file);
|
||||||
|
|
||||||
|
// Envoi de chaque fichier pour chaque client
|
||||||
|
return DocumentsNotary.getInstance().post(formData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attendre que tous les fichiers pour un client soient envoyés
|
||||||
|
await Promise.all(promises);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
router.push(
|
||||||
|
Module.getInstance()
|
||||||
|
.get()
|
||||||
|
.modules.pages.Folder.pages.FolderInformation.props.path.replace("[folderUid]", folderUid as string),
|
||||||
|
);
|
||||||
|
console.log("All files have been successfully sent.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error while sending files: ", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[files, folderUid, router, selectedClients],
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchFolder = useCallback(async () => {
|
const fetchFolder = useCallback(async () => {
|
||||||
@ -53,9 +88,23 @@ 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) {
|
||||||
|
const allClientIds = folder.customers.map((customer) => customer.uid ?? "");
|
||||||
|
setSelectedClients(allClientIds);
|
||||||
|
} else {
|
||||||
|
setSelectedClients([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[folder],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClientSelectionChange = useCallback((selectedOptions: any) => {
|
||||||
|
setSelectedClients(selectedOptions.map((option: any) => option.id));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -70,6 +119,10 @@ export default function SendDocuments() {
|
|||||||
[folderUid],
|
[folderUid],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleFileChange = useCallback((files: File[]) => {
|
||||||
|
setFiles(files);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const clientsOptions = useMemo(() => {
|
const clientsOptions = useMemo(() => {
|
||||||
if (!folder?.customers) return [];
|
if (!folder?.customers) return [];
|
||||||
return folder.customers.map((customer) => ({
|
return folder.customers.map((customer) => ({
|
||||||
@ -105,11 +158,20 @@ export default function SendDocuments() {
|
|||||||
|
|
||||||
<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
|
||||||
|
name="files"
|
||||||
|
title="Glisser ou déposer ou"
|
||||||
|
description="Formats acceptés : PDF, JPG Taille maximale : 5 Mo"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className={classes["buttons-container"]}>
|
<div className={classes["buttons-container"]}>
|
||||||
<a href={backUrl}>
|
<a href={backUrl}>
|
||||||
|
@ -30,7 +30,6 @@ export default function Folder() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((folders) => {
|
.then((folders) => {
|
||||||
console.log(folders);
|
|
||||||
if (folders.length > 0)
|
if (folders.length > 0)
|
||||||
router.push(
|
router.push(
|
||||||
Module.getInstance()
|
Module.getInstance()
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user