Maxime Lalo be78f7216c Small fixes
2023-12-13 16:45:40 +01:00

434 lines
13 KiB
TypeScript

import DepositDocumentIcon from "@Assets/Icons/deposit-document.svg";
import PlusIcon from "@Assets/Icons/plus.svg";
import CrossIcon from "@Assets/Icons/cross.svg";
import DocumentCheckIcon from "@Assets/Icons/document-check.svg";
import Image from "next/image";
import React from "react";
import Button, { EButtonVariant } from "../Button";
import Tooltip from "../ToolTip";
import Typography, { ITypo, ITypoColor } from "../Typography";
import classes from "./classes.module.scss";
import { Document, DocumentHistory, File as FileCustomer } from "le-coffre-resources/dist/Customer";
import Files from "@Front/Api/LeCoffreApi/Customer/Files/Files";
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
import classNames from "classnames";
import Confirm from "../Modal/Confirm";
import Alert from "../Modal/Alert";
import GreenCheckIcon from "@Assets/Icons/green-check.svg";
import Loader from "../Loader";
import TextAreaField from "../Form/TextareaField";
type IProps = {
defaultFiles?: FileCustomer[];
onChange?: (files: File[]) => void;
document: Document;
};
type IFile = {
index: number;
file: File;
uid: string;
archived: Date | null;
fileName: string;
};
type IState = {
files: IFile[];
isDragOver: boolean;
currentFiles?: FileCustomer[];
refusedReason?: string;
isShowRefusedReasonModalVisible: boolean;
showFailedUploaded: string | null;
loading: boolean;
};
type fileAccepted = {
extension: string;
size: number;
};
const filesAccepted: { [key: string]: fileAccepted } = {
"application/pdf": {
extension: "pdf",
size: 41943040,
},
"image/jpeg": {
extension: "jpeg",
size: 41943040,
},
"image/png": {
extension: "png",
size: 41943040,
},
"image/jpg": {
extension: "jpg",
size: 41943040,
},
};
export default class DepositDocument extends React.Component<IProps, IState> {
private inputRef = React.createRef<HTMLInputElement>();
private index = 0;
public constructor(props: IProps) {
super(props);
this.state = {
files: [],
isDragOver: false,
currentFiles: this.props.defaultFiles,
refusedReason: "",
isShowRefusedReasonModalVisible: false,
showFailedUploaded: null,
loading: false,
};
this.addDocument = this.addDocument.bind(this);
this.onFileChange = this.onFileChange.bind(this);
this.removeFile = this.removeFile.bind(this);
this.onDragOver = this.onDragOver.bind(this);
this.onDragDrop = this.onDragDrop.bind(this);
this.onDragLeave = this.onDragLeave.bind(this);
this.onCloseModalShowRefusedReason = this.onCloseModalShowRefusedReason.bind(this);
this.onOpenModalShowRefusedReason = this.onOpenModalShowRefusedReason.bind(this);
this.showRefusedReason = this.showRefusedReason.bind(this);
this.onCloseAlertUpload = this.onCloseAlertUpload.bind(this);
}
public override render(): JSX.Element {
return (
<div className={classes["container"]}>
<div
className={classNames(
classes["root"],
this.props.document.document_status === EDocumentStatus.VALIDATED && classes["validated"],
)}
onDragOver={this.onDragOver}
onDrop={this.onDragDrop}
onDragLeave={this.onDragLeave}
data-drag-over={this.state.isDragOver.toString()}>
<input
type="file"
ref={this.inputRef}
hidden
onChange={this.onFileChange}
accept={Object.keys(filesAccepted).join(",")}
/>
<div className={classes["top-container"]}>
<div className={classes["left"]}>
<Image src={DepositDocumentIcon} alt="Deposit document" />
</div>
<div className={classes["separator"]} />
<div className={classes["right"]}>
<Typography typo={ITypo.P_SB_16} color={ITypoColor.BLACK} className={classes["title"]}>
<div
className={
this.props.document.document_status === EDocumentStatus.VALIDATED ? classes["validated"] : ""
}>
{this.props.document.document_type?.name}
</div>
{this.props.document.document_type?.public_description !== " " &&
this.props.document.document_type?.public_description !== "" &&
this.props.document.document_status !== EDocumentStatus.VALIDATED && (
<Tooltip text={this.props.document.document_type?.public_description} />
)}
{this.props.document.document_status === EDocumentStatus.VALIDATED && (
<Image src={GreenCheckIcon} alt="Document check" />
)}
</Typography>
{this.props.document.document_status !== EDocumentStatus.VALIDATED && (
<Typography color={ITypoColor.GREY} typo={ITypo.CAPTION_14}>
Sélectionnez des documents .jpg, .pdf ou .png
</Typography>
)}
{this.props.document.document_history?.map((history) => (
<div key={history.uid}>{this.renderDocumentHistory(history)}</div>
))}
</div>
</div>
{this.props.document.document_status !== EDocumentStatus.VALIDATED && this.state.files.length > 0 && (
<div className={classes["documents-container"]}>
{this.state.files.map((file) => {
const fileObj = file.file;
if (file.archived) return;
return (
<div className={classes["file-container"]} key={fileObj.name + file.index}>
<div className={classes["left-part"]}>
<Image src={DocumentCheckIcon} alt="Document check" />
<Typography typo={ITypo.P_16} color={ITypoColor.GREY} title={file.fileName ?? fileObj.name}>
{this.shortName(file.fileName || fileObj.name)}
</Typography>
</div>
<Image
src={CrossIcon}
alt="Cross icon"
className={classes["cross"]}
onClick={this.removeFile}
data-file={file.index}
/>
</div>
);
})}
{this.state.loading && (
<div className={classes["file-container"]}>
<div className={classes["left-part"]}>
<div className={classes["loader"]}>
<Loader />
</div>
<Typography typo={ITypo.P_16} color={ITypoColor.GREY}>
Chargement...
</Typography>
</div>
<div />
</div>
)}
</div>
)}
{this.props.document.document_status !== EDocumentStatus.VALIDATED && (
<div className={classes["bottom-container"]}>
<Button variant={EButtonVariant.LINE} className={classes["add-button"]} onClick={this.addDocument}>
<Typography typo={ITypo.P_SB_16} color={ITypoColor.PINK_FLASH} className={classes["add-document"]}>
Ajouter un document <Image src={PlusIcon} alt="Plus icon" />
</Typography>
</Button>
</div>
)}
<Confirm
isOpen={this.state.isShowRefusedReasonModalVisible}
onClose={this.onCloseModalShowRefusedReason}
showCancelButton={false}
onAccept={this.onCloseModalShowRefusedReason}
closeBtn
header={"Motif du refus"}
confirmText={"J'ai compris"}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
Votre document a é refusé pour la raison suivante :
</Typography>
<TextAreaField placeholder="Description" defaultValue={this.state.refusedReason} readonly />
</div>
</Confirm>
</div>
{this.props.document.document_status === EDocumentStatus.REFUSED && (
<Typography typo={ITypo.CAPTION_14} className={classes["error-message"]}>
Ce document n'est pas conforme. Veuillez le déposer à nouveau.
</Typography>
)}
{this.state.showFailedUploaded && (
<Alert onClose={this.onCloseAlertUpload} header={"Fichier non autorisé"} isOpen={!!this.state.showFailedUploaded}>
<div className={classes["modal-content"]}>
<Typography typo={ITypo.P_16} className={classes["text"]}>
{this.state.showFailedUploaded}
</Typography>
</div>
</Alert>
)}
</div>
);
}
public override componentDidMount(): void {
if (this.props.defaultFiles) {
this.setState({
files: this.props.defaultFiles.map((file) => ({
index: this.index++,
file: new File([""], file.file_path ?? "", {}),
uid: file.uid!,
fileName: file.file_name,
archived: file.archived_at ? new Date(file.archived_at) : null,
})),
});
}
}
private onCloseModalShowRefusedReason() {
this.setState({
isShowRefusedReasonModalVisible: false,
});
}
private onOpenModalShowRefusedReason() {
this.setState({
isShowRefusedReasonModalVisible: true,
});
}
private renderDocumentHistory(history: DocumentHistory): JSX.Element | null {
switch (history.document_status) {
case EDocumentStatus.ASKED:
return (
<Typography color={ITypoColor.GREY} typo={ITypo.CAPTION_14}>
Demandé par votre notaire le {this.formatDate(history.created_at!)}
</Typography>
);
case EDocumentStatus.VALIDATED:
return (
<Typography color={ITypoColor.GREY} typo={ITypo.CAPTION_14}>
Validé par votre notaire le {this.formatDate(history.created_at!)}
</Typography>
);
case EDocumentStatus.DEPOSITED:
return (
<Typography color={ITypoColor.GREY} typo={ITypo.CAPTION_14}>
Déposé le {this.formatDate(history.created_at!)}
</Typography>
);
case EDocumentStatus.REFUSED:
return (
<Typography typo={ITypo.CAPTION_14} color={ITypoColor.RE_HOVER}>
Document non conforme
{history.refused_reason && history.refused_reason.length > 0 && (
<Button
variant={EButtonVariant.LINE}
className={classes["refused-button"]}
onClick={() => this.showRefusedReason(history.refused_reason ?? "")}>
Voir le motif de refus
</Button>
)}
</Typography>
);
}
return null;
}
private shortName(name: string): string {
const maxLength = 20;
if (name.length > maxLength) {
return name.substring(0, maxLength / 2) + "..." + name.substring(name.length - maxLength / 2, name.length);
}
return name;
}
private onDragOver(event: React.DragEvent<HTMLDivElement>) {
if (!this.state.isDragOver) {
this.setState({
isDragOver: true,
});
}
event.preventDefault();
}
private showRefusedReason(refusedReason: string) {
this.setState({
refusedReason,
});
this.onOpenModalShowRefusedReason();
}
private onDragLeave(event: React.DragEvent<HTMLDivElement>) {
this.setState({
isDragOver: false,
});
event.preventDefault();
}
private async onDragDrop(event: React.DragEvent<HTMLDivElement>) {
event.preventDefault();
this.setState({
isDragOver: false,
});
const file = event.dataTransfer.files[0];
if (file) this.addFile(file);
}
private async addFile(file: File) {
const fileAccepted = filesAccepted[file.type];
if (!fileAccepted) {
alert("Ce type de fichier n'est pas accepté");
return false;
}
if (file.size > fileAccepted.size) {
alert("Ce fichier est trop volumineux");
return false;
}
this.setState({
loading: true,
});
const formData = new FormData();
formData.append("file", file, file.name);
const query = JSON.stringify({ document: { uid: this.props.document.uid } });
formData.append("q", query);
let newFile: FileCustomer;
try {
newFile = await Files.getInstance().post(formData);
} catch (e) {
this.setState({ showFailedUploaded: "Le fichier ne correspond pas aux critères demandés", loading: false });
return false;
}
const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
const newFileList = [
...this.state.files,
{
index: this.index++,
file: file,
uid: newFile.uid!,
archived: null,
fileName: newFile?.file_name ?? "",
},
];
this.setState(
{
currentFiles: files,
loading: false,
files: newFileList,
},
() => {
if (this.props.onChange) this.props.onChange(newFileList.map((file) => file.file));
},
);
return true;
}
private async removeFile(e: any) {
const image = e.target as HTMLElement;
const indexToRemove = image.getAttribute("data-file");
if (!indexToRemove) return;
const file = this.state.files.find((file) => file.index === parseInt(indexToRemove));
if (!file) return;
this.setState({
files: this.state.files.filter((file) => file.index !== parseInt(indexToRemove)),
});
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
await Files.getInstance().delete(file.uid);
}
private async onFileChange() {
if (!this.inputRef.current) return;
const files = this.inputRef.current.files;
if (!files) {
this.setState({ loading: false });
return;
}
const file = files[0];
try {
if (file) this.addFile(file);
} catch (e) {
console.log(e);
}
}
private onCloseAlertUpload() {
this.setState({ showFailedUploaded: null });
}
private addDocument() {
if (!this.inputRef.current) return;
this.inputRef.current.value = "";
this.inputRef.current.click();
}
private formatDate(date: Date) {
const dateToConvert = new Date(date);
return dateToConvert.toLocaleDateString("fr-FR");
}
}