/** * DocumentsPanel — Shared documents tab for the meeting right rail. * * Features: * - Upload files (txt/md/pdf) * - Add URL links * - List documents with type icons * - Preview text files * - Download files * - Delete documents * * Additive — lives inside MeetingRightRail as a tab. */ import React, { useEffect, useMemo, useState, useRef } from 'react'; import { FileText, Link as LinkIcon, Upload, Trash2, Eye, Download, Plus, X, } from 'lucide-react'; import { useTeamsDocuments } from './useTeamsDocuments'; // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function DocumentsPanel({ backendUrl, apiKey, roomId, initialDocuments, onDocumentsChanged, }) { const docsApi = useTeamsDocuments(backendUrl, apiKey); const [docs, setDocs] = useState(initialDocuments || []); const [selectedId, setSelectedId] = useState(null); const [previewText, setPreviewText] = useState(''); const [busy, setBusy] = useState(false); const [showUrlForm, setShowUrlForm] = useState(false); const fileInputRef = useRef(null); const selected = useMemo(() => docs.find((d) => d.id === selectedId) || null, [docs, selectedId]); // Lazy refresh on mount useEffect(() => { ; (async () => { try { const list = await docsApi.list(roomId); setDocs(list); onDocumentsChanged?.(list); } catch { // keep local state } })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [roomId]); const iconFor = (d) => { if (d.kind === 'url') return ; const t = d.type; if (t === 'pdf') return ; if (t === 'md') return ; return ; }; const sizeLabel = (bytes) => { if (typeof bytes !== 'number') return ''; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; async function handleUpload(file) { setBusy(true); try { const res = await docsApi.upload(roomId, file); const list = await docsApi.list(roomId); setDocs(list); onDocumentsChanged?.(list); setSelectedId(res.document.id); } catch (e) { console.error('Upload failed:', e); } finally { setBusy(false); } } async function handlePreview(docId) { setBusy(true); try { const text = await docsApi.preview(roomId, docId); setPreviewText(text); setSelectedId(docId); } catch (e) { console.error('Preview failed:', e); setPreviewText('[Failed to load preview]'); } finally { setBusy(false); } } async function handleDelete(docId) { setBusy(true); try { await docsApi.deleteDoc(roomId, docId); const list = await docsApi.list(roomId); setDocs(list); onDocumentsChanged?.(list); if (selectedId === docId) { setSelectedId(null); setPreviewText(''); } } catch (e) { console.error('Delete failed:', e); } finally { setBusy(false); } } async function handleDownload(doc) { setBusy(true); try { await docsApi.download(roomId, doc); } catch (e) { console.error('Download failed:', e); } finally { setBusy(false); } } return (
{/* Actions row */}
{ const f = e.target.files?.[0]; if (f) handleUpload(f); e.currentTarget.value = ''; }}/>
{/* URL form */} {showUrlForm && ( { setBusy(true); try { await docsApi.addUrl(roomId, url, title); const list = await docsApi.list(roomId); setDocs(list); onDocumentsChanged?.(list); setShowUrlForm(false); } catch (e) { console.error('Add URL failed:', e); } finally { setBusy(false); } }} onCancel={() => setShowUrlForm(false)}/>)} {/* Document list */}
{docs.length === 0 && (

No shared documents yet

Upload a file or add a link

)} {docs.map((d) => (
{iconFor(d)}
{d.name}
{d.kind === 'url' ? 'Link' : (d.type || 'file').toUpperCase()} {d.size_bytes ? ` · ${sizeLabel(d.size_bytes)}` : ''} {d.uploaded_by ? ` · ${d.uploaded_by}` : ''}
{/* Action buttons */}
{d.kind === 'file' && ()}
))}
{/* Preview pane */} {selected && previewText && (
{selected.name}
{selected.kind === 'file' && selected.type === 'pdf' && ( Open PDF )}
            {previewText}
          
)}
); } // --------------------------------------------------------------------------- // Sub-component: Add URL form // --------------------------------------------------------------------------- function AddUrlForm({ disabled, onAdd, onCancel, }) { const [url, setUrl] = useState(''); const [title, setTitle] = useState(''); return (
setTitle(e.target.value)} placeholder="Title (optional)" className="w-full px-2.5 py-1.5 rounded-lg bg-white/[0.04] border border-white/[0.06] text-[10px] text-white/70 placeholder:text-white/15 focus:outline-none focus:border-cyan-500/30"/>
setUrl(e.target.value)} placeholder="https://..." className="flex-1 px-2.5 py-1.5 rounded-lg bg-white/[0.04] border border-white/[0.06] text-[10px] text-white/70 placeholder:text-white/15 focus:outline-none focus:border-cyan-500/30" onKeyDown={(e) => { if (e.key === 'Enter' && url.trim()) { onAdd(url.trim(), title.trim()); setUrl(''); setTitle(''); } }}/>
); }