"use client"; import { AlertCircle, Clock, Download, Eye, History, MoreHorizontal, Move, Pencil, RotateCcw, Trash2, } from "lucide-react"; import React, { useCallback, useRef, useState } from "react"; import { useDrag } from "react-dnd"; import { getDocumentTypeIcon } from "@/components/documents/DocumentTypeIcon"; import { ExportContextItems, ExportDropdownItems } from "@/components/shared/ExportMenuItems"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import type { DocumentTypeEnum } from "@/contracts/types/document.types"; import { cn } from "@/lib/utils"; import { DND_TYPES } from "./FolderNode"; import { isVersionableType } from "./version-history"; const EDITABLE_DOCUMENT_TYPES = new Set(["FILE", "NOTE"]); export interface DocumentNodeDoc { id: number; title: string; document_type: string; folderId: number | null; status?: { state: string; reason?: string | null }; } interface DocumentNodeProps { doc: DocumentNodeDoc; depth: number; isMentioned: boolean; onToggleChatMention: (doc: DocumentNodeDoc, isMentioned: boolean) => void; onPreview: (doc: DocumentNodeDoc) => void; onEdit: (doc: DocumentNodeDoc) => void; onDelete: (doc: DocumentNodeDoc) => void; onMove: (doc: DocumentNodeDoc) => void; onReset?: (doc: DocumentNodeDoc) => void; onExport?: (doc: DocumentNodeDoc, format: string) => void; onVersionHistory?: (doc: DocumentNodeDoc) => void; canDelete?: boolean; canMove?: boolean; canMention?: boolean; canEdit?: boolean; contextMenuOpen?: boolean; onContextMenuOpenChange?: (open: boolean) => void; } export const DocumentNode = React.memo(function DocumentNode({ doc, depth, isMentioned, onToggleChatMention, onPreview, onEdit, onDelete, onMove, onReset, onExport, onVersionHistory, canDelete = true, canMove = true, canMention = true, canEdit = true, contextMenuOpen, onContextMenuOpenChange, }: DocumentNodeProps) { const statusState = doc.status?.state ?? "ready"; const isFailed = statusState === "failed"; const isProcessing = statusState === "pending" || statusState === "processing"; const isUnavailable = isProcessing || isFailed; const isMemoryDocument = doc.document_type === "USER_MEMORY" || doc.document_type === "TEAM_MEMORY"; const isSelectable = canMention && !isUnavailable; const isEditable = canEdit && (isMemoryDocument || EDITABLE_DOCUMENT_TYPES.has(doc.document_type)) && !isUnavailable; const handleCheckChange = useCallback(() => { if (isSelectable) { onToggleChatMention(doc, isMentioned); } }, [doc, isMentioned, isSelectable, onToggleChatMention]); const handlePrimaryClick = useCallback(() => { if (canMention) { handleCheckChange(); return; } onPreview(doc); }, [canMention, doc, handleCheckChange, onPreview]); const [{ isDragging }, drag] = useDrag( () => ({ type: DND_TYPES.DOCUMENT, item: { id: doc.id }, canDrag: canMove, collect: (monitor) => ({ isDragging: monitor.isDragging() }), }), [canMove, doc.id] ); const [dropdownOpen, setDropdownOpen] = useState(false); const [exporting, setExporting] = useState(null); const [titleTooltipOpen, setTitleTooltipOpen] = useState(false); const rowRef = useRef(null); const titleRef = useRef(null); const handleExport = useCallback( (format: string) => { if (!onExport) return; setExporting(format); onExport(doc, format); setTimeout(() => setExporting(null), 2000); }, [doc, onExport] ); const handleTitleTooltipOpenChange = useCallback((open: boolean) => { if (open && titleRef.current) { setTitleTooltipOpen(titleRef.current.scrollWidth > titleRef.current.clientWidth); } else { setTitleTooltipOpen(false); } }, []); const attachRef = useCallback( (node: HTMLDivElement | null) => { (rowRef as React.MutableRefObject).current = node; if (canMove) { drag(node); } }, [canMove, drag] ); return (
{(() => { if (statusState === "pending") { return ( Pending: waiting to be synced ); } if (statusState === "processing") { return ( Syncing ); } if (statusState === "failed") { return ( {doc.status?.reason || "Processing failed"} ); } return ( <> {isMemoryDocument ? ( ) : canMention ? ( e.stopPropagation()} className="h-3.5 w-3.5 shrink-0" /> ) : ( {getDocumentTypeIcon( doc.document_type as DocumentTypeEnum, "h-3.5 w-3.5 text-muted-foreground" )} )} ); })()} {doc.title} {getDocumentTypeIcon( doc.document_type as DocumentTypeEnum, "h-3.5 w-3.5 text-muted-foreground" ) && ( {getDocumentTypeIcon( doc.document_type as DocumentTypeEnum, "h-3.5 w-3.5 text-muted-foreground" )} )} e.stopPropagation()} > onPreview(doc)} disabled={isUnavailable}> Open {isEditable && ( onEdit(doc)}> Edit )} {canMove && ( onMove(doc)}> Move to... )} {onExport && ( Export )} {onVersionHistory && isVersionableType(doc.document_type) && ( onVersionHistory(doc)}> Versions )} {isMemoryDocument && onReset && ( onReset(doc)}> Reset )} {canDelete && ( onDelete(doc)}> Delete )}
{contextMenuOpen && ( e.stopPropagation()}> onPreview(doc)} disabled={isUnavailable}> Open {isEditable && ( onEdit(doc)}> Edit )} {canMove && ( onMove(doc)}> Move to... )} {onExport && ( Export )} {onVersionHistory && isVersionableType(doc.document_type) && ( onVersionHistory(doc)}> Versions )} {isMemoryDocument && onReset && ( onReset(doc)}> Reset )} {canDelete && ( onDelete(doc)}> Delete )} )}
); });