'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { useLanguage } from '@/lib/i18n' import { FileText, Loader2, MessageSquare, Trash2, AlertCircle, Plus } from 'lucide-react' import { toast } from 'sonner' interface Attachment { id: string fileName: string fileSize: number mimeType: string status: 'pending' | 'processing' | 'ready' | 'failed' pageCount: number | null error: string | null createdAt: string } interface NoteAttachmentsProps { noteId: string onOpenDocQA: (attachment: Attachment) => void onCountChange?: (count: number) => void triggerUpload?: number } function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` return `${(bytes / (1024 * 1024)).toFixed(1)} MB` } export function NoteAttachments({ noteId, onOpenDocQA, onCountChange, triggerUpload }: NoteAttachmentsProps) { const { t } = useLanguage() const [attachments, setAttachments] = useState([]) const [uploading, setUploading] = useState(false) const [loading, setLoading] = useState(true) const fileInputRef = useRef(null) const sectionRef = useRef(null) const pollingRef = useRef | null>(null) const fetchAttachments = useCallback(async () => { try { const res = await fetch(`/api/notes/${noteId}/attachments`) if (res.ok) { const data = await res.json() const list = data.data || [] setAttachments(list) onCountChange?.(list.length) return list } } catch {} return [] }, [noteId]) useEffect(() => { fetchAttachments().finally(() => setLoading(false)) }, [fetchAttachments]) useEffect(() => { onCountChange?.(attachments.length) }, [attachments.length, onCountChange]) useEffect(() => { const hasPending = attachments.some(a => a.status === 'pending' || a.status === 'processing') if (hasPending) { pollingRef.current = setInterval(fetchAttachments, 3000) } else if (pollingRef.current) { clearInterval(pollingRef.current) pollingRef.current = null } return () => { if (pollingRef.current) clearInterval(pollingRef.current) } }, [attachments, fetchAttachments]) const lastTriggerRef = useRef(0) useEffect(() => { if (triggerUpload && triggerUpload > lastTriggerRef.current) { lastTriggerRef.current = triggerUpload if (attachments.length > 0) { sectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }) } fileInputRef.current?.click() } }, [triggerUpload, attachments.length]) const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return if (file.type !== 'application/pdf') { toast.error(t('attachments.onlyPdf') || 'Only PDF files are supported') return } if (file.size > 20 * 1024 * 1024) { toast.error(t('attachments.maxSize') || 'File too large (max 20MB)') return } setUploading(true) try { const formData = new FormData() formData.append('file', file) const res = await fetch(`/api/notes/${noteId}/attachments`, { method: 'POST', body: formData, }) if (res.ok) { const data = await res.json() setAttachments(prev => [data.data, ...prev]) toast.success(t('attachments.uploaded') || 'File uploaded — analyzing...') await fetchAttachments() setTimeout(() => { sectionRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }) }, 200) } else { const err = await res.json() toast.error(err.error || t('attachments.uploadFailed') || 'Upload failed') } } catch { toast.error(t('attachments.uploadError') || 'Upload error') } finally { setUploading(false) if (fileInputRef.current) fileInputRef.current.value = '' } } const handleDelete = async (attachmentId: string) => { try { const res = await fetch(`/api/notes/${noteId}/attachments/${attachmentId}`, { method: 'DELETE' }) if (res.ok) { setAttachments(prev => prev.filter(a => a.id !== attachmentId)) toast.success(t('attachments.deleted') || 'Attachment removed') } } catch {} } if (loading) return null return ( <> {attachments.length > 0 && (

{t('attachments.title') || 'Documents'}

{attachments.map(att => (
{(att.status === 'pending' || att.status === 'processing') && (

{att.fileName}

{formatFileSize(att.fileSize)}

{t('attachments.analyzing') || 'Analyzing document...'}
)} {att.status === 'ready' && ( )} {att.status === 'failed' && (

{att.fileName}

{att.error || (t('attachments.processingFailed') || 'Processing failed')}

)}
))}
)} {uploading && (
{t('attachments.uploading') || 'Uploading...'}
)} ) }