import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography"; import { DocumentPlusIcon } from "@heroicons/react/24/outline"; import classNames from "classnames"; import React, { useCallback, useEffect, useRef, useState } from "react"; import Button, { EButtonSize, EButtonstyletype, EButtonVariant } from "../Button"; import Separator, { ESeperatorColor, ESeperatorDirection } from "../Separator"; import classes from "./classes.module.scss"; import DocumentFileElement from "./DocumentFileElement"; /** * @description Drag and drop component to upload files * @param {string} title - Title of the component * @param {string} description - Description of the component * @param {IDocumentFileWithUid[]} defaultFiles - Default files to display * @param {(fileUid: string) => Promise} onDelete - Function to delete a file (must be used with defaultFiles) * @param {(file: File) => Promise} onAddFile - Function to add a file (must be used with defaultFiles) */ type IProps = { title: string; description?: string; defaultFiles?: IDocumentFileWithUid[]; onDelete?: (fileUid: string) => Promise; onAddFile?: (file: File) => Promise; onAddToList?: (file: IDocumentFile) => void; onRemoveFromList?: (file: IDocumentFile) => void; } & ( | { onDelete: (fileUid: string) => Promise; onAddFile?: never; defaultFiles: IDocumentFileWithUid[] } | { onDelete?: never; onAddFile: (file: File) => Promise; defaultFiles: IDocumentFileWithUid[] } | { onDelete?: (fileUid: string) => Promise; onAddFile?: (file: File) => Promise; defaultFiles: IDocumentFileWithUid[] } | { onDelete?: never; onAddFile?: never; defaultFiles?: never } ); type IMimeTypes = { extension: string; size: number; }; const mimeTypesAccepted: { [key: string]: IMimeTypes } = { "application/pdf": { extension: "pdf", size: 41943040, }, "image/jpeg": { extension: "jpeg", size: 41943040, }, "image/png": { extension: "png", size: 41943040, }, "image/jpg": { extension: "jpg", size: 41943040, }, }; type IDocumentFileBase = { id: string; file: File | null; uid?: string; isLoading?: boolean; error?: string; }; export type IDocumentFileWithUid = IDocumentFileBase & { uid: string; }; type IDocumentFile = IDocumentFileBase | IDocumentFileWithUid; export default function DragAndDrop(props: IProps) { const { title, description, defaultFiles, onDelete, onAddFile, onAddToList, onRemoveFromList } = props; const fileInputRef = useRef(null); const [documentFiles, setDocumentFiles] = useState([]); useEffect(() => { if (defaultFiles) { setDocumentFiles(defaultFiles); } }, [defaultFiles]); const handleAddFiles = useCallback( (files: File[]) => { files.forEach((file) => { setDocumentFiles((prevDocs) => [...prevDocs, { id: file.name, file: file, isLoading: true }]); try { if (!mimeTypesAccepted[file.type]) { throw new Error("Type de fichier non accepté"); } const newDoc: IDocumentFile = { id: file.name, file, isLoading: false }; if (onAddFile) { // As onAddFile is used along defaultFiles prop we dont need to update the state here but the parent component should update the defaultFiles prop return onAddFile(file); } return setTimeout(async () => { if (onAddToList) { onAddToList(newDoc); } setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === newDoc.id ? newDoc : doc))); }, 1000); } catch (error: any) { const errorDoc: IDocumentFile = { id: file.name, file: null, isLoading: false, error: error.message }; return setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === errorDoc.id ? errorDoc : doc))); } }); }, [onAddFile], ); const handleDrop = useCallback( (event: React.DragEvent) => { event.preventDefault(); const files = Array.from(event.dataTransfer.files); handleAddFiles(files); }, [handleAddFiles], ); const handleRemove = useCallback( (documentFile: IDocumentFile) => { if (onRemoveFromList) { onRemoveFromList(documentFile); } const loadingDoc = { ...documentFile, isLoading: true }; setDocumentFiles((prevDocs) => prevDocs.map((doc) => (doc.id === documentFile.id ? loadingDoc : doc))); if (documentFile.uid) { return onDelete?.(documentFile.uid); } return setDocumentFiles((prevDocs) => prevDocs.filter((doc) => doc.id !== documentFile.id)); }, [onDelete], ); const handleBrowse = useCallback( (event: React.ChangeEvent) => { const files = Array.from(event.target.files || []); handleAddFiles(files); }, [handleAddFiles], ); const triggerFileInput = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; return (
0 && classes["filled"])} onDrop={handleDrop} onDragOver={(e) => e.preventDefault()}>
{title}
{description && ( {description} )}
{documentFiles.length > 0 && (
{documentFiles.map((documentFile) => ( handleRemove(documentFile)} error={documentFile.error} /> ))}
)}
); function inputFile() { return ( ); } }