import React, { useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { CloudArrowUpIcon, PaperClipIcon } from '@heroicons/react/24/outline'; import { Attachment } from '../../../entities/Attachment'; import { uploadAttachment, deleteAttachment, downloadAttachment, fetchAttachments, validateFile, getAttachmentType, } from '../../../utils/attachmentsService'; import { useToast } from '../../Shared/ToastContext'; import ConfirmDialog from '../../Shared/ConfirmDialog'; import AttachmentCard from '../../Shared/AttachmentCard'; import AttachmentPreview from '../../Shared/AttachmentPreview'; interface TaskAttachmentsCardProps { taskUid: string; onAttachmentsCountChange?: (count: number) => void; } const TaskAttachmentsCard: React.FC = ({ taskUid, onAttachmentsCountChange, }) => { const { t } = useTranslation(); const { showSuccessToast, showErrorToast } = useToast(); const [attachments, setAttachments] = useState([]); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [previewAttachment, setPreviewAttachment] = useState(null); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [attachmentToDelete, setAttachmentToDelete] = useState(null); const fileInputRef = useRef(null); // Load attachments on mount useEffect(() => { loadAttachments(); }, [taskUid]); // Handle Escape key to close preview modal useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape' && previewAttachment) { setPreviewAttachment(null); } }; document.addEventListener('keydown', handleEscape); return () => { document.removeEventListener('keydown', handleEscape); }; }, [previewAttachment]); const loadAttachments = async () => { try { setLoading(true); const data = await fetchAttachments(taskUid); setAttachments(data); onAttachmentsCountChange?.(data.length); } catch (error) { console.error('Error loading attachments:', error); } finally { setLoading(false); } }; const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // Validate file const validation = await validateFile(file); if (!validation.valid) { showErrorToast(validation.error || 'Invalid file'); if (fileInputRef.current) { fileInputRef.current.value = ''; } return; } // Check attachment limit if (attachments.length >= 20) { showErrorToast( t( 'task.attachments.limitReached', 'Maximum 20 attachments allowed per task' ) ); if (fileInputRef.current) { fileInputRef.current.value = ''; } return; } // Upload file setUploading(true); try { const newAttachment = await uploadAttachment(taskUid, file); const updatedAttachments = [...attachments, newAttachment]; setAttachments(updatedAttachments); onAttachmentsCountChange?.(updatedAttachments.length); showSuccessToast( t( 'task.attachments.uploadSuccess', 'File uploaded successfully' ) ); } catch (error: any) { showErrorToast( error.message || t('task.attachments.uploadError', 'Failed to upload file') ); } finally { setUploading(false); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handleDelete = (attachment: Attachment) => { setAttachmentToDelete(attachment); setIsConfirmDialogOpen(true); }; const handleDeleteConfirm = async () => { if (!attachmentToDelete) return; try { await deleteAttachment(taskUid, attachmentToDelete.uid); const updatedAttachments = attachments.filter( (a) => a.uid !== attachmentToDelete.uid ); setAttachments(updatedAttachments); onAttachmentsCountChange?.(updatedAttachments.length); showSuccessToast( t( 'task.attachments.deleteSuccess', 'Attachment deleted successfully' ) ); if (previewAttachment?.uid === attachmentToDelete.uid) { setPreviewAttachment(null); } } catch (error: any) { showErrorToast( error.message || t( 'task.attachments.deleteError', 'Failed to delete attachment' ) ); } finally { setIsConfirmDialogOpen(false); setAttachmentToDelete(null); } }; const handleDownload = (attachment: Attachment) => { downloadAttachment(attachment.uid); }; const handlePreview = (attachment: Attachment) => { setPreviewAttachment( previewAttachment?.uid === attachment.uid ? null : attachment ); }; if (loading) { return (

{t('task.attachments.title', 'Attachments')}

{t('common.loading', 'Loading...')}

); } return (

{t('task.attachments.title', 'Attachments')} ( {attachments.length})

{/* Grid Layout for Cards */}
{/* Upload Card */}
!uploading && fileInputRef.current?.click()} >

{uploading ? t( 'task.attachments.uploading', 'Uploading...' ) : t( 'task.attachments.clickToUpload', 'Click to upload' )}

{t('task.attachments.maxSize', 'Max 10MB')}

{t( 'task.attachments.supportedFormats', 'PDF, images, docs & more' )}

{/* Attachment Cards */} {attachments.map((attachment) => ( ))}
{/* Full Preview Modal */} {previewAttachment && (
setPreviewAttachment(null)} >
e.stopPropagation()} >

{previewAttachment.original_filename}

)} {/* Confirm Delete Dialog */} {isConfirmDialogOpen && attachmentToDelete && ( { setIsConfirmDialogOpen(false); setAttachmentToDelete(null); }} /> )}
); }; export default TaskAttachmentsCard;