This commit is contained in:
VincentAlamelle 2024-12-04 15:45:17 +01:00 committed by GitHub
commit 1640876eff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 746 additions and 73 deletions

View File

@ -35,4 +35,16 @@ export default class DocumentsNotary extends BaseCustomer {
return Promise.reject(err);
}
}
public async getByUid(uid: string, q?: any): Promise<DocumentNotary> {
const url = new URL(this.baseURl.concat(`/${uid}`));
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);
}
}
}

View File

@ -90,4 +90,14 @@ export default class Files extends BaseCustomer {
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);
}
}
}

View File

@ -7,6 +7,12 @@ export interface IGetDocumentRemindersparams {
where?: {};
include?: {};
orderBy?: {};
skip?: number;
take?: number;
}
export interface IGetDocumentRemindersCountresponse {
count: number;
}
// TODO Type getbyuid query params
@ -38,4 +44,14 @@ export default class DocumentReminders extends BaseNotary {
return Promise.reject(err);
}
}
public count = async (): Promise<IGetDocumentRemindersCountresponse> => {
const url = new URL(this.baseURl.concat("/count"));
try {
return await this.getRequest<IGetDocumentRemindersCountresponse>(url);
} catch (err) {
this.onError(err);
return Promise.reject(err);
}
};
}

View File

@ -161,7 +161,7 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
onClick={this.addDocument}>
<Typography
typo={ETypo.TEXT_MD_SEMIBOLD}
color={ETypoColor.COLOR_SECONDARY_500}
color={ETypoColor.COLOR_PRIMARY_500}
className={classes["add-document"]}>
Ajouter un document <Image src={PlusIcon} alt="Plus icon" />
</Typography>
@ -242,6 +242,7 @@ export default class DepositOtherDocument extends React.Component<IProps, IState
});
this.props.onClose!();
window.location.reload();
}
private shortName(name: string): string {

View File

@ -46,6 +46,10 @@
min-width: 85vw;
padding: 0;
}
&.fullheight {
min-height: 75vh;
}
}
.backdrop {

View File

@ -17,10 +17,11 @@ type IProps = {
secondButton?: IButtonProps;
fullwidth?: boolean;
fullscreen?: boolean;
fullheight?: boolean;
};
export default function Modal(props: IProps) {
const { isOpen, onClose, children, className, title, firstButton, secondButton, fullwidth, fullscreen } = props;
const { isOpen, onClose, children, className, title, firstButton, secondButton, fullwidth, fullscreen, fullheight } = props;
if (!isOpen) return null;
return (
@ -33,6 +34,7 @@ export default function Modal(props: IProps) {
className,
fullwidth && classes["fullwidth"],
fullscreen && classes["fullscreen"],
fullheight && classes["fullheight"],
)}>
<div className={classes["header"]}>
{title && <Typography typo={ETypo.TITLE_H4}> {title}</Typography>}

View File

@ -53,7 +53,7 @@ export default function MuiTable(props: IProps) {
<InfiniteScroll orientation="vertical" onNext={props.onNext} offset={0}>
<TableContainer
className={classes["root"]}
sx={{ maxHeight: "80vh", overflowY: "auto", overflowX: "hidden", backgroundColor: "var(--table-background-default)" }}>
sx={{ height: "45vh", overflowY: "auto", overflowX: "hidden", backgroundColor: "var(--table-background-default)" }}>
<Table aria-label="simple table" sx={{ border: "0" }}>
<TableHead
sx={{
@ -93,8 +93,7 @@ export default function MuiTable(props: IProps) {
<Typography
className={classes["content"]}
typo={ETypo.TEXT_MD_REGULAR}
color={ETypoColor.COLOR_NEUTRAL_900}
>
color={ETypoColor.COLOR_NEUTRAL_900}>
{cell.value && typeof cell.value === "object" && "content" in cell.value
? cell.value.content
: cell.value}

View File

@ -17,6 +17,10 @@
text-decoration: underline !important;
cursor: pointer;
}
.date {
color: var(--color-primary-500);
}
}
@media screen and (max-width: $screen-s) {

View File

@ -72,18 +72,42 @@ export default function DepositDocumentComponent(props: IProps) {
{document.document_type?.name ?? "_"}
</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Demandé le: {document.created_at ? new Date(document.created_at).toLocaleDateString() : "_"}
Demandé le :{" "}
<a className={classes["date"]}>{document.created_at ? new Date(document.created_at).toLocaleDateString() : "_"}</a>
</Typography>
{document.document_status === "DEPOSITED" && (
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Déposé le :{" "}
<a className={classes["date"]}>
{document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_"}
</a>
</Typography>
</div>
)}
{document.document_status === "REFUSED" && (
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Refusé le : {document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_"}
Refusé le :{" "}
<a className={classes["date"]}>
{document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_"}
</a>
</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.ERROR_WEAK_CONTRAST} onClick={onOpenModal}>
Document non-conforme{" : "} <a className={classes["refused-link"]}>Voir le motif de refus</a>
</Typography>
</div>
)}
{document.document_status === "VALIDATED" && (
<div>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.TEXT_SECONDARY}>
Validé le :{" "}
<a className={classes["date"]}>
{document.updated_at ? new Date(document.updated_at).toLocaleDateString() : "_"}
</a>
</Typography>
</div>
)}
</div>
<Confirm
@ -105,13 +129,15 @@ export default function DepositDocumentComponent(props: IProps) {
</div>
</Confirm>
<DragAndDrop
title={"Glisser-déposer ou"}
description={document.document_type?.public_description ?? undefined}
defaultFiles={defaultFiles}
onAddFile={addFile}
onDelete={deleteFile}
/>
{document.document_status !== "VALIDATED" && (
<DragAndDrop
title={"Glisser-déposer ou"}
description={document.document_type?.public_description ?? undefined}
defaultFiles={defaultFiles}
onAddFile={addFile}
onDelete={deleteFile}
/>
)}
</div>
);
}

View File

@ -9,7 +9,7 @@ import BackArrow from "@Front/Components/Elements/BackArrow";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import Module from "@Front/Config/Module";
import JwtService from "@Front/Services/JwtService/JwtService";
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import { ArrowDownTrayIcon, EyeIcon } from "@heroicons/react/24/outline";
import { saveAs } from "file-saver";
import JSZip from "jszip";
import DocumentNotary from "le-coffre-resources/dist/Notary/DocumentNotary";
@ -17,6 +17,7 @@ import { useRouter } from "next/router";
import React, { useCallback, useEffect, useState } from "react";
import classes from "./classes.module.scss";
import Link from "next/link";
const header: readonly IHead[] = [
{
@ -100,7 +101,7 @@ export default function ReceivedDocuments() {
<Typography typo={ETypo.TITLE_H1} color={ETypoColor.TEXT_PRIMARY}>
Un document vous a é envoyé
</Typography>
<Table className={classes["table"]} header={header} rows={buildRows(documentsNotary, onDownload)} />
<Table className={classes["table"]} header={header} rows={buildRows(documentsNotary, folderUid!, onDownload)} />
<Button
variant={EButtonVariant.PRIMARY}
size={EButtonSize.LG}
@ -114,12 +115,34 @@ export default function ReceivedDocuments() {
);
}
function buildRows(documentsNotary: DocumentNotary[], onDownloadFileNotary: (doc: DocumentNotary) => void): IRowProps[] {
function buildRows(
documentsNotary: DocumentNotary[],
folderUid: string | string[],
onDownloadFileNotary: (doc: DocumentNotary) => void,
): IRowProps[] {
console.log(documentsNotary);
console.log(folderUid);
return documentsNotary.map((documentNotary) => ({
key: documentNotary.uid ?? "",
name: formatName(documentNotary.files?.[0]?.file_name?.split(".")?.[0] ?? "") || "_",
sentAt: new Date(documentNotary.created_at!).toLocaleDateString(),
actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />,
// actions: <IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />,
actions: {
sx: { width: 76 },
content: (
<div className={classes["actions"]}>
<Link
href={Module.getInstance()
.get()
.modules.pages.ClientDashboard.pages.ViewDocuments.props.path.replace("[folderUid]", folderUid as string)
.replace("[documentUid]", documentNotary.uid ?? "")}>
<IconButton icon={<EyeIcon />} />
</Link>
<IconButton onClick={() => onDownloadFileNotary(documentNotary)} icon={<ArrowDownTrayIcon />} />
</div>
),
},
}));
}

View File

@ -0,0 +1,77 @@
@import "@Themes/constants.scss";
.root {
.title {
margin-top: 24px;
}
.subtitle {
margin-top: 32px;
}
.document-container {
margin-top: 32px;
gap: 64px;
display: flex;
justify-content: space-between;
align-items: center;
.arrow-container {
cursor: pointer;
&[data-disabled="true"] {
opacity: 0.3;
cursor: not-allowed;
}
}
.file-container {
max-width: 1000px;
margin: auto;
min-height: 600px;
flex: 1;
}
.unsuported-format {
text-align: center;
}
}
.footer {
margin: auto;
.ocr-container {
margin-top: 42px;
}
.alert {
margin-top: 24px;
width: 100%;
}
.buttons-container {
display: flex;
gap: 24px;
justify-content: center;
margin-top: 32px;
@media (max-width: $screen-s) {
flex-direction: column-reverse;
}
}
}
.refuse-document-container {
.refuse-text {
margin-bottom: 24px;
}
}
.validate-document-container {
.document-validating-container {
.validate-gif {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
}

View File

@ -0,0 +1,242 @@
import LeftArrowIcon from "@Assets/Icons/left-arrow.svg";
import RightArrowIcon from "@Assets/Icons/right-arrow.svg";
import Button from "@Front/Components/DesignSystem/Button";
import FilePreview from "@Front/Components/DesignSystem/FilePreview";
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import { DocumentNotary, File } from "le-coffre-resources/dist/Notary";
import Image from "next/image";
import { NextRouter, useRouter } from "next/router";
import React from "react";
import BasePage from "../../Base";
import classes from "./classes.module.scss";
import DocumentsNotary from "@Front/Api/LeCoffreApi/Customer/DocumentsNotary/DocumentsNotary";
import DefaultTemplate from "@Front/Components/LayoutTemplates/DefaultTemplate";
import FilesNotary from "@Front/Api/LeCoffreApi/Customer/FilesNotary/Files";
type IProps = {};
type IPropsClass = {
documentUid: string;
router: NextRouter;
};
type IState = {
isRefuseModalVisible: boolean;
isValidateModalVisible: boolean;
refuseText: string;
selectedFileIndex: number;
selectedFile: File | null;
documentNotary: DocumentNotary | null;
fileBlob: Blob | null;
isLoading: boolean;
};
class ViewDocumentsNotaryClass extends BasePage<IPropsClass, IState> {
public constructor(props: IPropsClass) {
super(props);
this.state = {
isValidateModalVisible: false,
isRefuseModalVisible: false,
refuseText: "",
selectedFileIndex: 0,
selectedFile: null,
documentNotary: null,
fileBlob: null,
isLoading: true,
};
this.goToNext = this.goToNext.bind(this);
this.goToPrevious = this.goToPrevious.bind(this);
this.hasPrevious = this.hasPrevious.bind(this);
this.hasNext = this.hasNext.bind(this);
this.downloadFile = this.downloadFile.bind(this);
}
public override render(): JSX.Element | null {
return (
<DefaultTemplate title={"Visualiser le document"} hasBackArrow>
{this.state.documentNotary && this.state.documentNotary.files && this.state.selectedFile && !this.state.isLoading && (
<div className={classes["root"]}>
<Typography typo={ETypo.TITLE_H5} color={ETypoColor.COLOR_GENERIC_BLACK} className={classes["subtitle"]}>
{this.state.documentNotary.name}
</Typography>
<div className={classes["document-container"]}>
{this.state.documentNotary.files.length > 1 && (
<div
className={classes["arrow-container"]}
onClick={this.goToPrevious}
data-disabled={(!this.hasPrevious()).toString()}>
<Image src={LeftArrowIcon} alt="left arrow" />
</div>
)}
<div className={classes["file-container"]}>
{this.state.selectedFile.mimetype !== "text/csv" &&
this.state.selectedFile.mimetype !==
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" &&
this.state.selectedFile.mimetype !== "application/vnd.ms-excel" && (
<FilePreview
href={this.state.fileBlob ? URL.createObjectURL(this.state.fileBlob) : ""}
fileName={this.state.selectedFile.file_name}
key={this.state.selectedFile.uid}
/>
)}
{this.state.selectedFile.mimetype === "text/csv" && (
<div className={classes["unsuported-format"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK}>
L'affichage de ce format de fichier n'est pas supporté.
</Typography>
</div>
)}
{this.state.selectedFile.mimetype ===
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" && (
<div className={classes["unsuported-format"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK}>
L'affichage de ce format de fichier n'est pas supporté.
</Typography>
</div>
)}
{this.state.selectedFile.mimetype === "application/vnd.ms-excel" && (
<div className={classes["unsuported-format"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK}>
L'affichage de ce format de fichier n'est pas supporté.
</Typography>
</div>
)}
</div>
{this.state.documentNotary.files.length > 1 && (
<div
className={classes["arrow-container"]}
onClick={this.goToNext}
data-disabled={(!this.hasNext()).toString()}>
<Image src={RightArrowIcon} alt="right arrow" />
</div>
)}
</div>
<div className={classes["footer"]}>
{/* {this.state.document?.document_type?.name === "Document d'identité" && (
<div className={classes["ocr-container"]}>
<OcrResult percentage={this.state.validatedPercentage} />
</div>
)} */}
<div className={classes["buttons-container"]}>
<Button onClick={this.downloadFile}>Télécharger</Button>
</div>
</div>
</div>
)}
{(!this.state.selectedFile || !this.state.documentNotary) && !this.state.isLoading && (
<div className={classes["root"]}>
<Typography typo={ETypo.TEXT_MD_REGULAR} color={ETypoColor.COLOR_GENERIC_BLACK} className={classes["refuse-text"]}>
Document non trouvé
</Typography>
</div>
)}
</DefaultTemplate>
);
}
override async componentDidMount() {
try {
const documentNotary = await DocumentsNotary.getInstance().getByUid(this.props.documentUid, {
files: true,
folder: true,
depositor: true,
});
console.log(documentNotary);
this.setState(
{
documentNotary,
selectedFileIndex: 0,
selectedFile: documentNotary.files![0]!,
isLoading: false,
},
() => {
this.getFilePreview();
},
);
} catch (e) {
this.setState({
isLoading: false,
});
console.error(e);
}
}
private async getFilePreview(): Promise<void> {
try {
const fileBlob: Blob = await FilesNotary.getInstance().download(this.state.selectedFile?.uid as string, this.props.documentUid);
this.setState({
fileBlob,
});
} catch (e) {
console.error(e);
}
}
private downloadFile() {
if (!this.state.fileBlob) return;
const url = window.URL.createObjectURL(this.state.fileBlob);
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
// the filename you want
a.download = this.state.selectedFile?.file_name as string;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
}
private goToPrevious() {
const index = this.state.selectedFileIndex - 1;
if (this.hasPrevious()) {
this.setState(
{
selectedFile: this.state.documentNotary!.files![index]!,
selectedFileIndex: index,
fileBlob: null,
},
() => {
this.getFilePreview();
},
);
}
}
private goToNext() {
if (this.hasNext()) {
const index = this.state.selectedFileIndex + 1;
this.setState(
{
selectedFile: this.state.documentNotary!.files![index]!,
selectedFileIndex: index,
fileBlob: null,
},
() => {
this.getFilePreview();
},
);
}
}
private hasPrevious() {
const index = this.state.selectedFileIndex - 1;
return index >= 0;
}
private hasNext() {
const index = this.state.selectedFileIndex + 1;
return index < this.state.documentNotary!.files!.length;
}
}
export default function ViewDocumentsNotary(props: IProps) {
const router = useRouter();
let { folderUid, documentUid } = router.query;
documentUid = documentUid as string;
folderUid = folderUid as string;
return <ViewDocumentsNotaryClass {...props} documentUid={documentUid} router={router} />;
}

View File

@ -3,7 +3,7 @@ import Documents, { IGetDocumentsparams } from "@Front/Api/LeCoffreApi/Customer/
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
import Customer, { Document } from "le-coffre-resources/dist/Customer";
import Customer, { Document, DocumentType } from "le-coffre-resources/dist/Customer";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { DocumentNotary, type OfficeFolder as OfficeFolderNotary } from "le-coffre-resources/dist/Notary";
@ -12,20 +12,18 @@ import { useRouter } from "next/router";
import JwtService, { ICustomerJwtPayload } from "@Front/Services/JwtService/JwtService";
import Folders from "@Front/Api/LeCoffreApi/Customer/Folders/Folders";
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
import DefaultCustomerDashboard from "@Front/Components/LayoutTemplates/DefaultCustomerDashboard";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import DepositDocumentComponent from "./DepositDocumentComponent";
import Module from "@Front/Config/Module";
import Separator, { ESeperatorColor, ESeperatorDirection } from "@Front/Components/DesignSystem/Separator";
import NotificationBox from "@Front/Components/DesignSystem/NotificationBox";
import ContactBox from "./ContactBox";
import DocumentsNotary from "@Front/Api/LeCoffreApi/Customer/DocumentsNotary/DocumentsNotary";
import { EDocumentNotaryStatus } from "le-coffre-resources/dist/Notary/DocumentNotary";
// import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
import Tag, { ETagColor } from "@Front/Components/DesignSystem/Tag";
import DefaultCustomerDashboard from "@Front/Components/LayoutTemplates/DefaultCustomerDashboard";
import { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
import DepositDocumentComponent from "./DepositDocumentComponent";
import DepositOtherDocument from "@Front/Components/DesignSystem/DepositOtherDocument";
type IProps = {};
@ -37,7 +35,7 @@ export default function ClientDashboard(props: IProps) {
const [customer, setCustomer] = useState<Customer | null>(null);
const [folder, setFolder] = useState<OfficeFolderNotary | null>(null);
const [documentsNotary, setDocumentsNotary] = useState<DocumentNotary[]>([]);
// const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
const [isAddDocumentModalVisible, setIsAddDocumentModalVisible] = useState<boolean>(false);
const fetchFolderAndCustomer = useCallback(async () => {
let jwt: ICustomerJwtPayload | undefined;
@ -122,33 +120,31 @@ export default function ClientDashboard(props: IProps) {
[documentsNotary],
);
// const onCloseModalAddDocument = useCallback(() => {
// setIsAddDocumentModalVisible(false);
// fetchFolderAndCustomer();
// }, [fetchFolderAndCustomer]);
const onCloseModalAddDocument = useCallback(() => {
setIsAddDocumentModalVisible(false);
fetchFolderAndCustomer();
}, [fetchFolderAndCustomer]);
// const onOpenModalAddDocument = useCallback(() => {
// setIsAddDocumentModalVisible(true);
// }, []);
const onOpenModalAddDocument = useCallback(() => {
setIsAddDocumentModalVisible(true);
}, []);
// const renderBox = useCallback(() => {
// console.log(folder!.office!.uid);
// return (
// <DepositOtherDocument
// folder_uid={folderUid!}
// customer_uid={customer!.uid!}
// open={isAddDocumentModalVisible}
// onClose={onCloseModalAddDocument}
// document={Document.hydrate<Document>({
// document_type: DocumentType.hydrate<DocumentType>({
// name: "Autres documents",
// office: folder!.office!,
// }),
// })}
// />
// );
// }, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument]);
const renderBox = useCallback(() => {
return (
<DepositOtherDocument
folder_uid={folderUid!}
customer_uid={customer!.uid!}
open={isAddDocumentModalVisible}
onClose={onCloseModalAddDocument}
document={Document.hydrate<Document>({
document_type: DocumentType.hydrate<DocumentType>({
name: "Autres documents",
office: folder!.office!,
}),
})}
/>
);
}, [customer, folderUid, isAddDocumentModalVisible, onCloseModalAddDocument]);
return (
<DefaultCustomerDashboard>
@ -236,18 +232,18 @@ export default function ClientDashboard(props: IProps) {
))}
</div>
</div>
{/*<Typography typo={ETypo.TITLE_H3}>Documents supplémentaires (facultatif)</Typography>*/}
{/*<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
<Typography typo={ETypo.TITLE_H3}>Documents supplémentaires (facultatif)</Typography>
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["text"]}>
Vous souhaitez envoyer d'autres documents à votre notaire ?
</Typography>*/}
{/* <Button
</Typography>
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
className={classes["button"]}
onClick={onOpenModalAddDocument}>
Ajouter d'autres documents
</Button>
{isAddDocumentModalVisible && renderBox()} */}
{isAddDocumentModalVisible && renderBox()}
</div>
</DefaultCustomerDashboard>
);

View File

@ -56,7 +56,6 @@ export default function ParameterDocuments(props: IProps) {
}, []);
const handleClose = useCallback(() => {
setFormattedOptions([]);
setSelectedDocuments([]);
setAddOrEditDocument("edit");
setVisibleDescription("");
@ -83,6 +82,7 @@ export default function ParameterDocuments(props: IProps) {
//await this.loadData();
handleClose();
window.location.reload();
} catch (e) {
console.error(e);
}
@ -98,6 +98,7 @@ export default function ParameterDocuments(props: IProps) {
//await this.loadData();
handleClose();
window.location.reload();
} catch (e) {
console.error(e);
}
@ -122,7 +123,8 @@ export default function ParameterDocuments(props: IProps) {
onClose={handleClose}
firstButton={{ children: "Annuler", onClick: handleClose }}
secondButton={{ children: "Ajouter", onClick: addDocument }}
title={"Ajouter un document"}>
title={"Ajouter un document"}
fullheight>
<div className={classes["root"]}>
<div className={classes["radiobox-container"]}>
<RadioBox

View File

@ -8,11 +8,11 @@
align-items: flex-start;
gap: var(--spacing-xl, 32px);
.table{
.table {
width: 100%;
}
.customer-filter{
.customer-filter {
width: 472px;
}
@ -23,4 +23,74 @@
@media screen and (max-width: $screen-s) {
padding: var(--spacing-2);
}
.pagination {
display: flex;
align-items: center;
gap: 6px; /* Adds gap between each button */
.active {
background-color: var(--color-primary-800); /* Dark blue for active button */
border-color: var(--color-primary-800); /* Dark blue border for active button */
color: white; /* White text for active button */
}
button {
width: 50px; /* Fixed width */
height: 50px; /* Fixed height */
font-size: 14px;
text-align: center;
cursor: pointer;
transition: background-color 0.3s;
}
}
// .pagination button {
// width: 40px;
// height: 40px;
// background-color: var(--color-primary-500); /* Light blue background for inactive buttons */
// border: 1px solid var(--color-primary-500); /* Light blue border */
// color: #ffffff; /* Text color for inactive buttons */
// padding: 8px 16px;
// font-size: 14px;
// cursor: pointer;
// transition: background-color 0.3s;
// &:hover {
// background-color: #b2ebf2; /* Lighter blue on hover */
// }
// &.active {
// background-color: var(--color-primary-800); /* Dark blue for active button */
// border-color: var(--color-primary-800); /* Dark blue border for active button */
// color: white; /* White text for active button */
// }
// &.disabled {
// background-color: #f1f1f1; /* Light gray background for disabled buttons */
// color: #9e9e9e; /* Gray text for disabled buttons */
// cursor: not-allowed;
// }
// }
// .pagination button.prev,
// .pagination button.next {
// background-color: var(--color-primary-50); /* Dark teal background for Prev/Next buttons */
// color: var(--color-primary-500); /* White text for Prev/Next buttons */
// padding: 5px 10px;
// border-radius: 8px; /* Make the Prev/Next buttons circular */
// font-size: 14px;
// border: none; /* Remove the border for a cleaner look */
// }
// .pagination button.prev:hover,
// .pagination button.next:hover {
// border-color: var(--button-contained-neutral-hovered-border);
// background: var(--button-contained-neutral-hovered-background);
// }
// .pagination span {
// color: #00796b; /* Color for ellipsis */
// font-size: 14px;
// }
}

View File

@ -16,6 +16,7 @@ import { useRouter } from "next/router";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import classes from "./classes.module.scss";
import Button, { EButtonstyletype, EButtonVariant } from "@Front/Components/DesignSystem/Button";
type IProps = {};
@ -50,8 +51,21 @@ export default function DocumentsReminderHistory(props: IProps) {
const [customers, setCustomers] = useState<Customer[] | null>(null);
const [customerOption, setCustomerOption] = useState<IOption | null>(null);
const router = useRouter();
const [page, setPage] = useState(1); // Current page number
const [pageSize, setPageSize] = useState(10); // Number of items per page
const [totalPages, setTotalPages] = useState(1); // Total number of pages
let { folderUid } = router.query;
const fetchTotalPages = useCallback(() => {
DocumentReminders.getInstance()
.count()
.then((response) => {
const totalPages = Math.ceil(response.count / pageSize);
setTotalPages(totalPages);
})
.catch((e) => console.warn(e));
}, [pageSize]);
const fetchReminders = useCallback(() => {
DocumentReminders.getInstance()
.get({
@ -69,10 +83,14 @@ export default function DocumentsReminderHistory(props: IProps) {
},
},
orderBy: { reminder_date: "desc" },
skip: (page - 1) * pageSize, // Skip based on the page number
take: pageSize, // Take the number of items for the page
})
.then((response) => {
setReminders(response); // Set the reminders
})
.then((reminders) => setReminders(reminders))
.catch((e) => console.warn(e));
}, [customerOption]);
}, [customerOption, page, pageSize]); // Update on page change
const fetchCustomers = useCallback(async () => {
if (!folderUid) return;
@ -108,13 +126,64 @@ export default function DocumentsReminderHistory(props: IProps) {
}, [customers]);
useEffect(() => {
fetchTotalPages();
fetchReminders();
fetchCustomers();
}, [fetchCustomers, fetchReminders]);
}, [fetchTotalPages, fetchCustomers, fetchReminders]);
const onSelectionChange = useCallback((option: IOption | null) => {
setCustomerOption(option ?? null);
}, []);
const generatePageNumbers = (currentPage: number, totalPages: number) => {
const pageNumbers = [];
const maxPagesToShow = 7; // Maximum number of page buttons, including ellipsis
// Case 1: If total pages are <= 5, show all pages
if (totalPages <= 5) {
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
}
// Case 2: If current page is <= 3, and totalPages > 5, show the first 4 pages + ellipsis
else if (currentPage <= 3) {
for (let i = 1; i <= 4; i++) {
pageNumbers.push(i);
}
pageNumbers.push("...");
}
// Case 3: If current page is in the middle, show ellipses and surrounding pages
else if (currentPage > 3 && currentPage < totalPages - 2) {
pageNumbers.push("...");
pageNumbers.push(currentPage - 1);
pageNumbers.push(currentPage);
pageNumbers.push(currentPage + 1);
pageNumbers.push("...");
}
// Case 4: If current page is near the end, show ellipsis and last 4 pages
else if (currentPage >= totalPages - 2) {
pageNumbers.push("...");
for (let i = totalPages - 3; i <= totalPages; i++) {
pageNumbers.push(i);
}
}
// Ensure the pagination length is exactly maxPagesToShow (counting ellipses as one)
if (pageNumbers.length > maxPagesToShow) {
// Remove ellipsis at the start or end if pagination exceeds max length
if (pageNumbers[0] === "...") {
pageNumbers.shift(); // Remove first ellipsis if it's the first item
}
if (pageNumbers.length > maxPagesToShow && pageNumbers[pageNumbers.length - 1] === "...") {
pageNumbers.pop(); // Remove last ellipsis if it's the last item
}
}
return pageNumbers;
};
// Handle page change
const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages) {
setPage(newPage);
}
};
return (
<DefaultTemplate title={"Historique des relances de documents"} isPadding={false}>
@ -131,11 +200,81 @@ export default function DocumentsReminderHistory(props: IProps) {
<Dropdown
className={classes["customer-filter"]}
options={customersOptions}
onSelectionChange={onSelectionChange}
onSelectionChange={(option) => setCustomerOption(option ?? null)}
selectedOption={customerOption ?? customersOptions?.[0]}
label="Client"
/>
{/* Page Size Selector */}
<Dropdown
className={classes["page-size-selector"]}
options={[
{ id: "10", label: "10 par page" },
{ id: "20", label: "20 par page" },
{ id: "50", label: "50 par page" },
]}
selectedOption={{ id: `${pageSize}`, label: `${pageSize} par page` }}
onSelectionChange={(option) => {
setPageSize(parseInt(option.id, 10)); // Update the page size
setPage(1); // Reset the page to 1
}}
label="Items par page"
/>
<Table className={classes["table"]} header={header} rows={buildRows(reminders)} />
<div className={classes["pagination"]}>
{/* Left Arrow (Prev) */}
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
className={`${page === 1 ? classes["disabled"] : ""} ${classes["prev"]}`}
onClick={() => handlePageChange(page - 1)}>
&lt;
</Button>
{/* <button
className={`${page === 1 ? classes["disabled"] : ""} ${classes["prev"]}`}
onClick={() => handlePageChange(page - 1)}
aria-label="Previous Page">
&lt;
</button> */}
{/* Page numbers */}
{generatePageNumbers(page, totalPages).map((pageNum, index) => (
<React.Fragment key={index}>
{pageNum === "..." ? (
<Button variant={EButtonVariant.PRIMARY}>...</Button>
) : (
<Button
variant={EButtonVariant.PRIMARY}
className={page === pageNum ? classes["active"] : ""}
onClick={() => handlePageChange(pageNum as number)}>
{pageNum}
</Button>
// <button
// className={page === pageNum ? classes["active"] : ""}
// onClick={() => handlePageChange(pageNum as number)}
// aria-label={`Go to page ${pageNum}`}>
// {pageNum}
// </button>
)}
</React.Fragment>
))}
{/* Right Arrow (Next) */}
<Button
variant={EButtonVariant.PRIMARY}
styletype={EButtonstyletype.OUTLINED}
className={`${page === totalPages ? classes["disabled"] : ""} ${classes["next"]}`}
onClick={() => handlePageChange(page + 1)}>
&gt;
</Button>
{/* <button
className={`${page === totalPages ? classes["disabled"] : ""} ${classes["next"]}`}
onClick={() => handlePageChange(page + 1)}
aria-label="Next Page">
&gt;
</button> */}
</div>
</div>
</DefaultTemplate>
);

View File

@ -4,4 +4,5 @@
display: flex;
flex-direction: column;
gap: var(--spacing-xl, 32px);
width: 100%;
}

View File

@ -14,6 +14,7 @@ import classes from "./classes.module.scss";
import ClientBox from "./ClientBox";
import DocumentTables from "./DocumentTables";
import EmailReminder from "./EmailReminder";
import DocumentsNotary from "@Front/Api/LeCoffreApi/Notary/DocumentsNotary/DocumentsNotary";
type IProps = {
folder: OfficeFolder;
@ -57,8 +58,19 @@ export default function ClientView(props: IProps) {
);
const handleClientDelete = useCallback(
(customerUid: string) => {
async (customerUid: string) => {
if (!folder.uid) return;
const documentsNotary = await DocumentsNotary.getInstance().get({
where: { customer: { uid: customerUid }, folder: { uid: folder.uid } },
});
console.log(documentsNotary);
if (documentsNotary.length > 0) {
documentsNotary.forEach(async (doc) => {
await DocumentsNotary.getInstance().delete(doc.uid!);
});
}
Folders.getInstance().put(
folder.uid,
OfficeFolder.hydrate<OfficeFolder>({

View File

@ -148,9 +148,13 @@ export default function InformationSection(props: IProps) {
<IconButton icon={<PaperAirplaneIcon />} variant={EIconButtonVariant.NEUTRAL} />
</Link>
<Menu items={menuItemsDekstop}>
<IconButton icon={<EllipsisHorizontalIcon />} variant={EIconButtonVariant.NEUTRAL} />
</Menu>
{/* If the folder is not archived, we can display the archive button */}
{anchorStatus !== "VERIFIED_ON_CHAIN" && (
<Menu items={menuItemsDekstop}>
<IconButton icon={<EllipsisHorizontalIcon />} variant={EIconButtonVariant.NEUTRAL} />
</Menu>
)}
{!isArchived && <IconButton onClick={onArchive} icon={<ArchiveBoxIcon />} variant={EIconButtonVariant.ERROR} />}
</div>
</div>

View File

@ -38,6 +38,13 @@
"labelKey": "client-dashboard"
},
"pages": {
"ViewDocuments": {
"enabled": true,
"props": {
"path": "/client-dashboard/[folderUid]/documentNotary/[documentUid]",
"labelKey": "view_documents"
}
},
"ReceiveDocuments": {
"enabled": true,
"props": {

View File

@ -38,6 +38,13 @@
"labelKey": "client-dashboard"
},
"pages": {
"ViewDocuments": {
"enabled": true,
"props": {
"path": "/client-dashboard/[folderUid]/documentNotary/[documentUid]",
"labelKey": "view_documents"
}
},
"ReceiveDocuments": {
"enabled": true,
"props": {

View File

@ -38,6 +38,13 @@
"labelKey": "client-dashboard"
},
"pages": {
"ViewDocuments": {
"enabled": true,
"props": {
"path": "/client-dashboard/[folderUid]/documentNotary/[documentUid]",
"labelKey": "view_documents"
}
},
"ReceiveDocuments": {
"enabled": true,
"props": {

View File

@ -38,6 +38,13 @@
"labelKey": "client-dashboard"
},
"pages": {
"ViewDocuments": {
"enabled": true,
"props": {
"path": "/client-dashboard/[folderUid]/documentNotary/[documentUid]",
"labelKey": "view_documents"
}
},
"ReceiveDocuments": {
"enabled": true,
"props": {

View File

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