332 lines
10 KiB
TypeScript
332 lines
10 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/SuperAdmin/Files/Files";
|
||
import { EDocumentStatus } from "le-coffre-resources/dist/Customer/Document";
|
||
import classNames from "classnames";
|
||
import Confirm from "../Modal/Confirm";
|
||
import InputField from "../Form/Elements/InputField";
|
||
import GreenCheckIcon from "@Assets/Icons/green-check.svg";
|
||
|
||
type IProps = {
|
||
defaultFiles?: FileCustomer[];
|
||
onChange?: (files: File[]) => void;
|
||
document: Document;
|
||
};
|
||
|
||
type IFile = {
|
||
index: number;
|
||
file: File;
|
||
uid: string;
|
||
archived: Date | null;
|
||
};
|
||
|
||
type IState = {
|
||
files: IFile[];
|
||
isDragOver: boolean;
|
||
currentFiles?: FileCustomer[];
|
||
refusedReason?: string;
|
||
isShowRefusedReasonModalVisible: boolean;
|
||
};
|
||
|
||
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,
|
||
};
|
||
|
||
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);
|
||
}
|
||
|
||
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} />
|
||
<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_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}>
|
||
{this.shortName(fileObj.name)}
|
||
</Typography>
|
||
</div>
|
||
<Image
|
||
src={CrossIcon}
|
||
alt="Cross icon"
|
||
className={classes["cross"]}
|
||
onClick={this.removeFile}
|
||
data-file={file.index}
|
||
/>
|
||
</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 été refusé pour la raison suivante :
|
||
</Typography>
|
||
<InputField textarea fakeplaceholder={"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>
|
||
)}
|
||
</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!,
|
||
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 formData = new FormData();
|
||
formData.append("file", file, file.name);
|
||
const query = JSON.stringify({ document: { uid: this.props.document.uid } });
|
||
formData.append("q", query);
|
||
|
||
const newFile = await Files.getInstance().post(formData);
|
||
const files = this.state.currentFiles ? [...this.state.currentFiles, newFile] : [newFile];
|
||
|
||
this.setState({
|
||
currentFiles: files,
|
||
files: [
|
||
...this.state.files,
|
||
{
|
||
index: this.index++,
|
||
file: file,
|
||
uid: newFile.uid!,
|
||
archived: null,
|
||
},
|
||
],
|
||
});
|
||
|
||
if (this.props.onChange) this.props.onChange(this.state.files.map((file) => file.file));
|
||
}
|
||
|
||
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) return;
|
||
const file = files[0];
|
||
|
||
if (file) this.addFile(file);
|
||
}
|
||
|
||
private addDocument() {
|
||
if (!this.inputRef.current) return;
|
||
this.inputRef.current.click();
|
||
}
|
||
|
||
private formatDate(date: Date) {
|
||
const dateToConvert = new Date(date);
|
||
return dateToConvert.toLocaleDateString("fr-FR");
|
||
}
|
||
}
|