diff --git a/frontend/src/components/ImageUploadModal/ImageUploadForm.tsx b/frontend/src/components/ImageUploadModal/ImageUploadForm.tsx new file mode 100644 index 0000000..610cca7 --- /dev/null +++ b/frontend/src/components/ImageUploadModal/ImageUploadForm.tsx @@ -0,0 +1,271 @@ +import { useState, useEffect } from 'react'; +import { Button } from '../ui/button'; +import { cn } from '@/lib/utils'; +import useAuthStore from '@/stores/useAuthStore'; +import { CircleCheckBig, CircleDashed, CircleX, X } from 'lucide-react'; +import { FixedSizeList } from 'react-window'; +import { UseMutationResult } from '@tanstack/react-query'; +import { UploadZipParams, UploadFolderParams } from '@/types/uploadTypes'; + +interface UploadFormProps { + onClose: () => void; + onRefetch?: () => void; + onFileCount: (fileCount: number) => void; + projectId: number; + folderId: number; + isFolderUpload?: boolean; + isZipUpload?: boolean; + isFolderBackendUpload?: boolean; + uploadImageZipMutation: UseMutationResult; + uploadImageFolderFileMutation: UseMutationResult; + uploadImageFileMutation: UseMutationResult; + uploadImageFolderMutation: UseMutationResult; +} + +export default function ImageUploadForm({ + onClose, + onRefetch, + onFileCount, + projectId, + folderId, + isFolderUpload = false, + isZipUpload = false, + isFolderBackendUpload = false, + uploadImageZipMutation, + uploadImageFolderFileMutation, + uploadImageFileMutation, + uploadImageFolderMutation, +}: UploadFormProps) { + const profile = useAuthStore((state) => state.profile); + const memberId = profile?.id || 0; + + const [files, setFiles] = useState([]); + const [inputKey, setInputKey] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [isUploading, setIsUploading] = useState(false); + const [isUploaded, setIsUploaded] = useState(false); + const [isFailed, setIsFailed] = useState(false); + const [progress, setProgress] = useState(0); + + const handleClose = () => { + onClose(); + setFiles([]); + setInputKey((prevKey) => prevKey + 1); + setIsUploading(false); + setIsUploaded(false); + setIsFailed(false); + setProgress(0); + }; + + const handleRefetch = () => { + if (onRefetch) { + onRefetch(); + } + }; + + const handleChange = (event: React.ChangeEvent) => { + const newFiles = event.target.files; + + if (newFiles) { + setFiles((prevFiles) => [...prevFiles, ...Array.from(newFiles)]); + } + + event.target.value = ''; + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + setIsDragging(true); + }; + + const handleDragLeave = (event: React.DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + setIsDragging(false); + }; + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + setIsDragging(false); + const droppedFiles = event.dataTransfer.files; + if (droppedFiles) { + setFiles((prevFiles) => [...prevFiles, ...Array.from(droppedFiles)]); + } + }; + + const handleRemoveFile = (index: number) => { + if (isFolderUpload) { + setFiles([]); + setInputKey((prevKey) => prevKey + 1); + } else { + setFiles(files.filter((_, i) => i !== index)); + } + }; + + const handleUpload = () => { + if (files.length > 0) { + setIsUploading(true); + setIsUploaded(false); + setIsFailed(false); + + const progressCallback = (progress: number) => { + setProgress(progress); + }; + + if (isZipUpload) { + const variables: UploadZipParams = { + memberId, + projectId, + folderId, + file: files[0], + progressCallback, + }; + uploadImageZipMutation.mutate(variables, { + onSuccess: () => { + handleRefetch(); + setIsUploaded(true); + }, + onError: () => { + setIsFailed(true); + }, + }); + } else { + const variables: UploadFolderParams = { + memberId, + projectId, + folderId, + files, + progressCallback, + }; + const mutation = isFolderBackendUpload + ? uploadImageFolderMutation + : isFolderUpload + ? uploadImageFolderFileMutation + : uploadImageFileMutation; + + mutation.mutate(variables, { + onSuccess: () => { + handleRefetch(); + setIsUploaded(true); + }, + onError: () => { + setIsFailed(true); + }, + }); + } + } + }; + + useEffect(() => { + onFileCount(files.length); + }, [files, onFileCount]); + + return ( +
+ {!isUploading && ( +
+ + {isDragging ? ( +

+ {isFolderUpload ? '드래그한 폴더를 여기에 놓으세요' : '드래그한 파일을 여기에 놓으세요'} +

+ ) : ( +

+ {isFolderUpload + ? '폴더를 업로드하려면 여기를 클릭하거나 폴더를 드래그하여 여기에 놓으세요' + : '파일을 업로드하려면 여기를 클릭하거나 파일을 드래그하여 여기에 놓으세요'} +

+ )} +
+ )} + {files.length > 0 && ( +
    + + {({ index, style }) => ( +
  • + {files[index].webkitRelativePath || files[index].name} + {isUploading ? ( +
    + {isUploaded ? ( + + ) : isFailed ? ( + + ) : ( + + )} +
    + ) : ( + + )} +
  • + )} +
    +
+ )} + {isUploading ? ( + + ) : ( + + )} +
+ ); +} diff --git a/frontend/src/components/ImageUploadModal/index.tsx b/frontend/src/components/ImageUploadModal/index.tsx new file mode 100644 index 0000000..82a9d9f --- /dev/null +++ b/frontend/src/components/ImageUploadModal/index.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom'; +import { Plus } from 'lucide-react'; +import ImageUploadForm from './ImageUploadForm'; +import useUploadImageFileQuery from '@/queries/projects/useUploadImageFileQuery'; +import useUploadImageFolderFileQuery from '@/queries/projects/useUploadImageFolderFileQuery'; +import useUploadImageZipQuery from '@/queries/projects/useUploadImageZipQuery'; +import useUploadImageFolderQuery from '@/queries/projects/useUploadImageFolderQuery'; + +interface ImageUploadModalProps { + projectId: number; + folderId: number; + isFolderUpload?: boolean; + isZipUpload?: boolean; + isFolderBackendUpload?: boolean; +} + +export default function ImageUploadModal({ + projectId, + folderId, + isFolderUpload = false, + isZipUpload = false, + isFolderBackendUpload = false, +}: ImageUploadModalProps) { + const [isOpen, setIsOpen] = React.useState(false); + const [fileCount, setFileCount] = React.useState(0); + + const handleOpen = () => setIsOpen(true); + const handleClose = () => setIsOpen(false); + const handleFileCount = (fileCount: number) => { + setFileCount(fileCount); + }; + + const uploadImageZipMutation = useUploadImageZipQuery(); + const uploadImageFolderFileMutation = useUploadImageFolderFileQuery(); + const uploadImageFileMutation = useUploadImageFileQuery(); + const uploadImageFolderMutation = useUploadImageFolderQuery(); + + return ( + + + + + + 0 ? `파일 업로드 (${fileCount})` : '파일 업로드'} /> + + + + ); +}