import { useState, useMemo, useCallback } from "react"; import { FileText, ChevronDown, ChevronUp, ChevronRight, RefreshCw, Search, X } from "lucide-react"; import type { TaskDocumentWithTask, TaskDetail } from "@fusion/core"; import type { ToastType } from "../hooks/useToast"; import { fetchTaskDetail } from "../api"; import { useDocuments } from "../hooks/useDocuments"; export interface DocumentsViewProps { projectId?: string; addToast: (message: string, type?: ToastType) => void; onOpenDetail: (task: TaskDetail) => void; } interface DocumentCardProps { document: TaskDocumentWithTask; onOpenTask: (taskId: string) => void; } function formatTimestamp(iso?: string): string { if (!iso) return ""; return new Date(iso).toLocaleString(); } function getContentPreview(content: string, maxLength: number = 200): string { if (content.length <= maxLength) return content; return content.substring(0, maxLength) + "…"; } function DocumentCard({ document, onOpenTask }: DocumentCardProps) { const [expanded, setExpanded] = useState(false); const preview = getContentPreview(document.content); const showExpand = document.content.length > 200; return (
{document.key} v{document.revision}
{document.author} · {formatTimestamp(document.updatedAt)}
{expanded ? (
{document.content}
) : (

{preview}

)} {showExpand && !expanded && (

)}
); } interface TaskGroupProps { taskId: string; taskTitle?: string; documents: TaskDocumentWithTask[]; onOpenTask: (taskId: string) => void; } function TaskGroup({ taskId, taskTitle, documents, onOpenTask }: TaskGroupProps) { const [expanded, setExpanded] = useState(false); return (
{documents.length} doc{documents.length !== 1 ? "s" : ""} {expanded && (
{documents.map((doc) => ( ))}
)}
); } export function DocumentsView({ projectId, addToast, onOpenDetail }: DocumentsViewProps) { const [searchQuery, setSearchQuery] = useState(""); const { documents, loading, error, refresh } = useDocuments({ projectId, searchQuery: searchQuery || undefined, }); // Group documents by task const groupedDocuments = useMemo(() => { const groups = new Map(); for (const doc of documents) { const existing = groups.get(doc.taskId) || []; groups.set(doc.taskId, [...existing, doc]); } // Sort groups by the most recently updated document return Array.from(groups.entries()) .map(([taskId, docs]) => ({ taskId, taskTitle: docs[0].taskTitle, documents: docs.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)), latestUpdated: docs[0].updatedAt, })) .sort((a, b) => b.latestUpdated.localeCompare(a.latestUpdated)); }, [documents]); const handleSearchChange = useCallback((e: React.ChangeEvent) => { setSearchQuery(e.target.value); }, []); const clearSearch = useCallback(() => { setSearchQuery(""); }, []); // Wrapper to open task detail by fetching full task first const handleOpenTask = useCallback(async (taskId: string) => { try { const task = await fetchTaskDetail(taskId, projectId); onOpenDetail(task); } catch (err) { addToast(`Failed to open task ${taskId}`, "error"); } }, [projectId, onOpenDetail, addToast]); if (error) { return (

Failed to load documents: {error}

); } return (

Documents

{loading ? "…" : `${documents.length} total`}
{searchQuery && ( )}
{loading ? (

Loading documents…

) : groupedDocuments.length === 0 ? (
{searchQuery ? (

No documents match "{searchQuery}".

) : ( <>

No documents yet.

Documents are created in task detail tabs.

)}
) : (
{groupedDocuments.map(({ taskId, taskTitle, documents: taskDocs }) => ( ))}
)}
); }