import React, { useState, useRef, useEffect } from 'react'; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { FileUp, Loader2, File, FileText, Image, Film, Music, Archive, ChevronDown, Search, HelpCircle } from "lucide-react"; import CustomDialog from '@/components/CustomDialog'; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; export interface FileUploadData { file: File; description: string; documentType?: string; // Optional document type field } // Document type interface export interface DocumentType { name: string; description: string; } interface UploadAttachmentDialogProps { isOpen: boolean; onClose: () => void; selectedFiles: FileUploadData[]; onUpdateDescription: (index: number, description: string) => void; onUpdateDocumentType?: (index: number, documentType: string) => void; // Optional handler for document type onUpload: () => void; isUploading: boolean; entityType: string; documentTypes?: DocumentType[] | string[]; // Can accept either structure } /** * A custom dialog component for uploading attachments with descriptions */ const UploadAttachmentDialog: React.FC = ({ isOpen, onClose, selectedFiles, onUpdateDescription, onUpdateDocumentType, onUpload, isUploading, entityType, documentTypes = [] }) => { // State for custom dropdowns const [openDropdowns, setOpenDropdowns] = useState>({}); const dropdownRefs = useRef>({}); const [dropdownPositions, setDropdownPositions] = useState>({}); const [dropdownWidths, setDropdownWidths] = useState>({}); const [searchQueries, setSearchQueries] = useState>({}); // Check if documentTypes has the new structure const hasDetailedDocTypes = documentTypes.length > 0 && typeof documentTypes[0] !== 'string' && 'name' in documentTypes[0]; // Helper to get file extension from filename const getFileExtension = (filename: string): string => { return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2); }; // Helper to get file icon based on file type const getFileIcon = (file: File) => { const fileType = file.type.toLowerCase(); const fileName = file.name.toLowerCase(); if (fileType.includes('image') || fileName.match(/\.(jpg|jpeg|png|gif|webp)$/i)) { return ; } if (fileType.includes('pdf') || fileName.endsWith('.pdf')) { return ; } if (fileType.includes('word') || fileName.match(/\.(doc|docx)$/i)) { return ; } if (fileType.includes('excel') || fileName.match(/\.(xls|xlsx)$/i)) { return ; } if (fileType.includes('video') || fileName.match(/\.(mp4|mov|avi|wmv)$/i)) { return ; } if (fileType.includes('audio') || fileName.match(/\.(mp3|wav|ogg)$/i)) { return ; } if (fileType.includes('zip') || fileName.match(/\.(zip|rar|7z|tar|gz)$/i)) { return ; } return ; }; const handleCancel = () => { if (!isUploading) { onClose(); } }; // Get normalized document types for filtering const getNormalizedDocTypes = (): Array<{ name: string, description: string }> => { if (hasDetailedDocTypes) { return documentTypes as DocumentType[]; } else { return (documentTypes as string[]).map(name => ({ name, description: "" })); } }; // Determine dropdown position when toggling open const toggleDropdown = (index: number) => { if (isUploading) return; if (!openDropdowns[index]) { // Only calculate position when opening const dropdownRef = dropdownRefs.current[index]; if (dropdownRef) { const rect = dropdownRef.getBoundingClientRect(); const spaceBelow = window.innerHeight - rect.bottom; const requiredSpace = Math.min(documentTypes.length * 28, 200) + 50; // Extra space for search box setDropdownPositions(prev => ({ ...prev, [index]: spaceBelow < requiredSpace ? 'top' : 'bottom' })); // Store the exact width setDropdownWidths(prev => ({ ...prev, [index]: rect.width })); } } setOpenDropdowns(prev => ({ ...prev, [index]: !prev[index] })); // Reset search when opening if (!openDropdowns[index]) { setSearchQueries(prev => ({ ...prev, [index]: '' })); } }; // Update search query const handleSearchChange = (index: number, value: string) => { setSearchQueries(prev => ({ ...prev, [index]: value })); }; // Handle selecting a document type const handleSelectDocumentType = (index: number, type: string) => { if (onUpdateDocumentType) { onUpdateDocumentType(index, type); } // Close the dropdown setOpenDropdowns(prev => ({ ...prev, [index]: false })); }; // Filter document types based on search query const getFilteredDocumentTypes = (index: number) => { const query = searchQueries[index] || ''; const normalizedDocTypes = getNormalizedDocTypes(); if (!query) return normalizedDocTypes; return normalizedDocTypes.filter(docType => docType.name.toLowerCase().includes(query.toLowerCase()) || docType.description.toLowerCase().includes(query.toLowerCase()) ); }; // Get document type name to display in dropdown const getDocumentTypeDisplay = (fileData: FileUploadData) => { if (!fileData.documentType) return "Select document type"; // If using new structure, find the matching document type if (hasDetailedDocTypes) { const docTypes = documentTypes as DocumentType[]; const foundType = docTypes.find(dt => dt.name === fileData.documentType); return foundType ? foundType.name : fileData.documentType; } return fileData.documentType; }; // Close dropdowns when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { Object.entries(dropdownRefs.current).forEach(([indexStr, ref]) => { const index = parseInt(indexStr); if (ref && !ref.contains(event.target as Node) && openDropdowns[index]) { setOpenDropdowns(prev => ({ ...prev, [index]: false })); } }); }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [openDropdowns]); // Determine if we should show document type selection const showDocumentTypes = documentTypes.length > 0 && onUpdateDocumentType; // Validation: Check if all files have a description and at least one file is selected const isFormValid = selectedFiles.length > 0 && selectedFiles.every(fileData => fileData.description.trim() !== ''); return (
{selectedFiles.map((fileData, index) => (
{getFileIcon(fileData.file)} {fileData.file.name} {(fileData.file.size / 1024).toFixed(1)} KB
{/* Custom Document Type Dropdown - Only show if document types are provided */} {showDocumentTypes && (
dropdownRefs.current[index] = ref} className="relative" >
toggleDropdown(index)} id={`doctype-${index}`} > {getDocumentTypeDisplay(fileData)}
{openDropdowns[index] && (
handleSearchChange(index, e.target.value)} className="pl-8 h-8 text-sm" onClick={(e) => e.stopPropagation()} />
{getFilteredDocumentTypes(index).length > 0 ? ( getFilteredDocumentTypes(index).map((docType) => (
handleSelectDocumentType(index, docType.name)} > {docType.name} {docType.description && ( )}
{docType.description && ( {docType.description} )}
)) ) : (
No matching document types
)}
)}
)}