"use client"; import { AlertCircle, Clock, Download, History, MoreHorizontal, Move, 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 { SidebarListItem } from "../layout/ui/sidebar/SidebarListItem"; import { DND_TYPES } from "./FolderNode"; import { isVersionableType } from "./version-history"; 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; 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; contextMenuOpen?: boolean; onContextMenuOpenChange?: (open: boolean) => void; } export const DocumentNode = React.memo(function DocumentNode({ doc, depth, isMentioned, onToggleChatMention, onPreview, onDelete, onMove, onReset, onExport, onVersionHistory, canDelete = true, canMove = true, canMention = 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 handleCheckChange = useCallback(() => { if (isSelectable) { onToggleChatMention(doc, isMentioned); } }, [doc, isMentioned, isSelectable, onToggleChatMention]); const handlePrimaryClick = useCallback(() => { if (isUnavailable) return; onPreview(doc); }, [doc, isUnavailable, onPreview]); const handlePrimaryKeyDown = useCallback( (event: React.KeyboardEvent) => { if (event.currentTarget !== event.target) return; if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); handlePrimaryClick(); }, [handlePrimaryClick] ); 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} {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()} > {canMove && ( onMove(doc)}> Move to... )} {onExport && isMemoryDocument ? ( handleExport("md")}> Export as MD ) : onExport ? ( Export ) : null} {onVersionHistory && isVersionableType(doc.document_type) && ( onVersionHistory(doc)}> Versions )} {isMemoryDocument && onReset && ( onReset(doc)}> Reset )} {canDelete && ( onDelete(doc)}> Delete )} {contextMenuOpen && ( e.stopPropagation()}> {canMove && ( onMove(doc)}> Move to... )} {onExport && isMemoryDocument ? ( handleExport("md")}> Export as MD ) : onExport ? ( Export ) : null} {onVersionHistory && isVersionableType(doc.document_type) && ( onVersionHistory(doc)}> Versions )} {isMemoryDocument && onReset && ( onReset(doc)}> Reset )} {canDelete && ( onDelete(doc)}> Delete )} )} ); });