"use client" import { useIsMobile } from "@hooks/use-mobile" import { cn } from "@lib/utils" import { Badge } from "@repo/ui/components/badge" import { Card, CardContent, CardHeader } from "@repo/ui/components/card" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@repo/ui/components/alert-dialog" import { colors } from "@repo/ui/memory-graph/constants" import type { DocumentsWithMemoriesResponseSchema } from "@repo/validation/api" import { useVirtualizer } from "@tanstack/react-virtual" import { Brain, ExternalLink, Sparkles, Trash2 } from "lucide-react" import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react" import type { z } from "zod" import useResizeObserver from "@/hooks/use-resize-observer" import { analytics } from "@/lib/analytics" import { useDeleteDocument } from "@lib/queries" import { useProject } from "@/stores" import { MemoryDetail } from "./memories-utils/memory-detail" import { getDocumentIcon } from "@/components/new/document-modal/document-icon" import { formatDate, getSourceUrl } from "./memories-utils" type DocumentsResponse = z.infer type DocumentWithMemories = DocumentsResponse["documents"][0] interface MemoryListViewProps { children?: React.ReactNode documents: DocumentWithMemories[] isLoading: boolean isLoadingMore: boolean error: Error | null totalLoaded: number hasMore: boolean loadMoreDocuments: () => Promise } const DocumentCard = memo( ({ document, onOpenDetails, onDelete, }: { document: DocumentWithMemories onOpenDetails: (document: DocumentWithMemories) => void onDelete: (document: DocumentWithMemories) => void }) => { const [isDialogOpen, setIsDialogOpen] = useState(false) const activeMemories = document.memoryEntries.filter((m) => !m.isForgotten) const forgottenMemories = document.memoryEntries.filter( (m) => m.isForgotten, ) return ( { if (!isDialogOpen) { analytics.documentCardClicked() onOpenDetails(document) } }} style={{ backgroundColor: colors.document.primary, }} >
{getDocumentIcon(document.type, "w-4 h-4 flex-shrink-0")}

{document.title || "Untitled Document"}

{document.url && ( )}
{formatDate(document.createdAt)}
{document.content && (

{document.content}

)}
{activeMemories.length > 0 && ( {activeMemories.length}{" "} {activeMemories.length === 1 ? "memory" : "memories"} )} {forgottenMemories.length > 0 && ( {forgottenMemories.length} forgotten )}
e.stopPropagation()}> Delete Document Are you sure you want to delete this document and all its related memories? This action cannot be undone. { e.stopPropagation() }} > Cancel { e.stopPropagation() onDelete(document) }} > Delete
) }, ) export const MemoryListView = ({ children, documents, isLoading, isLoadingMore, error, hasMore, loadMoreDocuments, }: MemoryListViewProps) => { const [selectedSpace, _] = useState("all") const [selectedDocument, setSelectedDocument] = useState(null) const [isDetailOpen, setIsDetailOpen] = useState(false) const parentRef = useRef(null) const containerRef = useRef(null) const isMobile = useIsMobile() const { selectedProject } = useProject() const deleteDocumentMutation = useDeleteDocument(selectedProject) const gap = 14 const handleDeleteDocument = useCallback( (document: DocumentWithMemories) => { deleteDocumentMutation.mutate(document.id) }, [deleteDocumentMutation], ) const { width: containerWidth } = useResizeObserver(containerRef) const columnWidth = isMobile ? containerWidth : 320 const columns = Math.max( 1, Math.floor((containerWidth + gap) / (columnWidth + gap)), ) // Filter documents based on selected space const filteredDocuments = useMemo(() => { if (!documents) return [] if (selectedSpace === "all") { return documents } return documents.map((doc) => ({ ...doc, memoryEntries: doc.memoryEntries.filter( (memory) => (memory.spaceContainerTag ?? memory.spaceId) === selectedSpace, ), })) }, [documents, selectedSpace]) const handleOpenDetails = useCallback((document: DocumentWithMemories) => { analytics.memoryDetailOpened() setSelectedDocument(document) setIsDetailOpen(true) }, []) const handleCloseDetails = useCallback(() => { setIsDetailOpen(false) setTimeout(() => setSelectedDocument(null), 300) }, []) const virtualItems = useMemo(() => { const items = [] for (let i = 0; i < filteredDocuments.length; i += columns) { items.push(filteredDocuments.slice(i, i + columns)) } return items }, [filteredDocuments, columns]) const virtualizer = useVirtualizer({ count: virtualItems.length, getScrollElement: () => parentRef.current, overscan: 5, estimateSize: () => 200, }) useEffect(() => { const [lastItem] = [...virtualizer.getVirtualItems()].reverse() if (!lastItem || !hasMore || isLoadingMore) { return } if (lastItem.index >= virtualItems.length - 1) { loadMoreDocuments() } }, [ hasMore, isLoadingMore, loadMoreDocuments, virtualizer.getVirtualItems, virtualItems.length, ]) // Always render with consistent structure return ( <>
{error ? (
Error loading documents: {error.message}
) : isLoading ? (
Loading memory list...
) : filteredDocuments.length === 0 && !isLoading ? (
{children}
) : (
{virtualizer.getVirtualItems().map((virtualRow) => { const rowItems = virtualItems[virtualRow.index] if (!rowItems) return null return (
{rowItems.map((document, columnIndex) => ( ))}
) })}
{isLoadingMore && (
Loading more memories...
)}
)}
) }