'use client'; import Link from 'next/link'; import { useEffect } from 'react'; import { useDrag, useDrop } from 'react-dnd'; import type { DocumentListDocument, SortBy, SortDirection, } from '@/types/documents'; import { PDFIcon, EPUBIcon, FileIcon } from '@/components/icons/Icons'; import { formatDocumentSize } from '@/components/doclist/formatSize'; import { IconButton } from '@/components/ui'; import { useDocumentSelection } from '../dnd/DocumentSelectionContext'; import { DND_DOCUMENT, documentIdentityKey, type DocumentDragItem } from '../dnd/dndTypes'; interface ListViewProps { documents: DocumentListDocument[]; sortBy: SortBy; sortDirection: SortDirection; onSortChange: (sortBy: SortBy, direction: SortDirection) => void; onDeleteDoc: (doc: DocumentListDocument) => void; onMergeIntoFolder: (sources: DocumentListDocument[], target: DocumentListDocument) => void; } function formatDate(ms: number): string { const d = new Date(ms); return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } function KindIcon({ doc }: { doc: DocumentListDocument }) { if (doc.type === 'pdf') return ; if (doc.type === 'epub') return ; return ; } function HeaderCell({ label, field, sortBy, sortDirection, onSortChange, className, align = 'left', }: { label: string; field: SortBy; sortBy: SortBy; sortDirection: SortDirection; onSortChange: (b: SortBy, d: SortDirection) => void; className?: string; align?: 'left' | 'right'; }) { const active = sortBy === field; const arrow = active ? (sortDirection === 'asc' ? '↑' : '↓') : ''; return ( ); } function DocRow({ doc, onDeleteDoc, onMergeIntoFolder, }: { doc: DocumentListDocument; onDeleteDoc: (d: DocumentListDocument) => void; onMergeIntoFolder: (sources: DocumentListDocument[], target: DocumentListDocument) => void; }) { const selection = useDocumentSelection(); const isSelected = selection.isSelected(doc); const isInFolder = Boolean(doc.folderId); const href = `/${doc.type}/${encodeURIComponent(doc.id)}`; const [{ isDragging }, dragRef] = useDrag(() => ({ type: DND_DOCUMENT, item: () => { const selected = selection.getSelectedDocs(); const dragging = isSelected && selected.length > 1 ? selected : [doc]; if (!isSelected) selection.replace([doc]); return { items: dragging.map(({ id, type }) => ({ id, type })), docs: dragging, fromFolderId: doc.folderId, }; }, collect: (m) => ({ isDragging: m.isDragging() }), }), [doc, isSelected, selection]); const [{ isOver, canDrop }, dropRef] = useDrop(() => ({ accept: DND_DOCUMENT, canDrop: (item) => !isInFolder && !item.items.some((it) => documentIdentityKey(it) === documentIdentityKey(doc)), drop: (item) => onMergeIntoFolder(item.docs, doc), collect: (m) => ({ isOver: m.isOver({ shallow: true }), canDrop: m.canDrop() }), }), [doc, isInFolder, onMergeIntoFolder]); const setRefs = (node: HTMLDivElement | null) => { dragRef(node); dropRef(node); }; const isTarget = isOver && canDrop; const handleClick: React.MouseEventHandler = (e) => { if (e.shiftKey || e.metaKey || e.ctrlKey) { e.preventDefault(); selection.select(doc, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey }); } }; return (
{doc.name} {doc.type} {formatDocumentSize(doc.size)} {formatDate(doc.lastModified)} { e.stopPropagation(); onDeleteDoc(doc); }} size="sm" aria-label={`Delete ${doc.name}`} >
); } export function ListView({ documents, sortBy, sortDirection, onSortChange, onDeleteDoc, onMergeIntoFolder, }: ListViewProps) { const { setVisibleOrder, clear } = useDocumentSelection(); useEffect(() => { setVisibleOrder(documents); }, [documents, setVisibleOrder]); const handleBackgroundClick: React.MouseEventHandler = (e) => { if ((e.target as HTMLElement).closest('[data-doc-tile]')) return; clear(); }; return (
{documents.map((doc) => ( ))}
); }