refactor(ui): gap closure Phase 2 — extract memory-documents-table

Split memory-page.tsx (314 → 244) by extracting the documents table
into memory-documents-table.tsx.

Remaining 14 files >300 lines assessed — no clean extraction
boundaries found (dense forms, tab routers, DnD contexts).
This commit is contained in:
viettranx committed 2026-04-03 23:32:16 +07:00
1 parent f6bca7003a
commit 4b6538839f
2 files changed
+164 -90

No files matched your search

@@ -0,0 +1,144 @@
import { Brain, RotateCw, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Pagination } from "@/components/shared/pagination";
import { EmptyState } from "@/components/shared/empty-state";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
import { formatUserLabel } from "@/lib/format-user-label";
import { useTranslation } from "react-i18next";
import type { MemoryDocument } from "@/types/memory";
type ContactResolver = (id: string) => { display_name?: string; username?: string } | null;
interface MemoryDocumentsTableProps {
documents: MemoryDocument[];
paginatedDocs: MemoryDocument[];
loading: boolean;
agentId: string;
agentWorkspace?: string;
page: number;
pageSize: number;
total: number;
totalPages: number;
resolveContact: ContactResolver;
agentMap: Map<string, string>;
onViewDoc: (doc: MemoryDocument) => void;
onDeleteTarget: (doc: MemoryDocument) => void;
onReindex: (doc: MemoryDocument) => void;
onPageChange: (page: number) => void;
onPageSizeChange: (size: number) => void;
}
export function MemoryDocumentsTable({
documents,
paginatedDocs,
loading,
agentId,
agentWorkspace,
page,
pageSize,
total,
totalPages,
resolveContact,
agentMap,
onViewDoc,
onDeleteTarget,
onReindex,
onPageChange,
onPageSizeChange,
}: MemoryDocumentsTableProps) {
const { t } = useTranslation("memory");
if (loading && documents.length === 0) {
return <TableSkeleton rows={5} />;
}
if (documents.length === 0) {
return (
<EmptyState
icon={Brain}
title={t("emptyTitle")}
description={agentId ? t("emptyAgentDescription") : t("emptyGlobalDescription")}
/>
);
}
return (
<div className="overflow-x-auto rounded-md border">
<table className="w-full min-w-[600px] text-sm">
<thead>
<tr className="border-b bg-muted/50">
<th className="px-4 py-3 text-left font-medium">{t("columns.path")}</th>
{!agentId && <th className="px-4 py-3 text-left font-medium">{t("columns.agent")}</th>}
<th className="px-4 py-3 text-left font-medium">{t("columns.scope")}</th>
<th className="px-4 py-3 text-left font-medium">{t("columns.hash")}</th>
<th className="px-4 py-3 text-left font-medium">{t("columns.updated")}</th>
<th className="px-4 py-3 text-right font-medium">{t("columns.actions")}</th>
</tr>
</thead>
<tbody>
{paginatedDocs.map((doc) => (
<tr key={`${doc.agent_id}-${doc.path}-${doc.user_id || "global"}`} className="border-b last:border-0 hover:bg-muted/30">
<td className="px-4 py-3">
<button
className="flex items-start gap-2 text-left hover:underline cursor-pointer"
onClick={() => onViewDoc(doc)}
>
<Brain className="h-4 w-4 shrink-0 text-muted-foreground mt-0.5" />
<div>
<span className="font-mono text-xs font-medium">{doc.path}</span>
{agentWorkspace && (
<p className="font-mono text-[10px] text-muted-foreground">{agentWorkspace}</p>
)}
</div>
</button>
</td>
{!agentId && (
<td className="px-4 py-3 text-xs text-muted-foreground">
{doc.agent_id ? (agentMap.get(doc.agent_id) || doc.agent_id.slice(0, 8)) : "-"}
</td>
)}
<td className="px-4 py-3">
<Badge variant={doc.user_id ? "secondary" : "outline"}>
{doc.user_id ? t("scopeLabel.personal") : t("scopeLabel.global")}
</Badge>
{doc.user_id && (
<span className="ml-1 text-xs text-muted-foreground">{formatUserLabel(doc.user_id, resolveContact)}</span>
)}
</td>
<td className="px-4 py-3 font-mono text-xs text-muted-foreground">
{doc.hash.slice(0, 8)}
</td>
<td className="px-4 py-3 text-xs text-muted-foreground">
{new Date(doc.updated_at).toLocaleString()}
</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" onClick={() => onReindex(doc)} className="gap-1">
<RotateCw className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => onDeleteTarget(doc)}
className="gap-1 text-destructive hover:text-destructive"
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
page={page}
pageSize={pageSize}
total={total}
totalPages={totalPages}
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/>
</div>
);
}
+20 -90
View File
@@ -1,13 +1,10 @@
import { useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Brain, Plus, RefreshCw, Search, Database, Trash2, RotateCw } from "lucide-react";
import { Plus, RefreshCw, Search, Database } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Label } from "@/components/ui/label";
import { PageHeader } from "@/components/shared/page-header";
import { EmptyState } from "@/components/shared/empty-state";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
import { Pagination } from "@/components/shared/pagination";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import { useAgents } from "@/pages/agents/hooks/use-agents";
import { useContactResolver } from "@/hooks/use-contact-resolver";
@@ -16,6 +13,7 @@ import { useMemoryDocuments } from "./hooks/use-memory";
import { MemoryDocumentDialog } from "./memory-document-dialog";
import { MemoryCreateDialog } from "./memory-create-dialog";
import { MemorySearchDialog } from "./memory-search-dialog";
import { MemoryDocumentsTable } from "./memory-documents-table";
import { useMinLoading } from "@/hooks/use-min-loading";
import { useDeferredLoading } from "@/hooks/use-deferred-loading";
import { useEmbeddingStatus } from "@/hooks/use-embedding-status";
@@ -189,92 +187,24 @@ export function MemoryPage() {
{/* Document table */}
<div className="mt-4">
{showSkeleton ? (
<TableSkeleton rows={5} />
) : documents.length === 0 ? (
<EmptyState
icon={Brain}
title={t("emptyTitle")}
description={agentId ? t("emptyAgentDescription") : t("emptyGlobalDescription")}
/>
) : (
<div className="overflow-x-auto rounded-md border">
<table className="w-full min-w-[600px] text-sm">
<thead>
<tr className="border-b bg-muted/50">
<th className="px-4 py-3 text-left font-medium">{t("columns.path")}</th>
{!agentId && <th className="px-4 py-3 text-left font-medium">{t("columns.agent")}</th>}
<th className="px-4 py-3 text-left font-medium">{t("columns.scope")}</th>
<th className="px-4 py-3 text-left font-medium">{t("columns.hash")}</th>
<th className="px-4 py-3 text-left font-medium">{t("columns.updated")}</th>
<th className="px-4 py-3 text-right font-medium">{t("columns.actions")}</th>
</tr>
</thead>
<tbody>
{paginatedDocs.map((doc) => (
<tr key={`${doc.agent_id}-${doc.path}-${doc.user_id || "global"}`} className="border-b last:border-0 hover:bg-muted/30">
<td className="px-4 py-3">
<button
className="flex items-start gap-2 text-left hover:underline cursor-pointer"
onClick={() => setViewDoc(doc)}
>
<Brain className="h-4 w-4 shrink-0 text-muted-foreground mt-0.5" />
<div>
<span className="font-mono text-xs font-medium">{doc.path}</span>
{selectedAgent?.workspace && (
<p className="font-mono text-[10px] text-muted-foreground">{selectedAgent.workspace}</p>
)}
</div>
</button>
</td>
{!agentId && (
<td className="px-4 py-3 text-xs text-muted-foreground">
{doc.agent_id ? (agentMap.get(doc.agent_id) || doc.agent_id.slice(0, 8)) : "-"}
</td>
)}
<td className="px-4 py-3">
<Badge variant={doc.user_id ? "secondary" : "outline"}>
{doc.user_id ? t("scopeLabel.personal") : t("scopeLabel.global")}
</Badge>
{doc.user_id && (
<span className="ml-1 text-xs text-muted-foreground">{formatUserLabel(doc.user_id, resolveContact)}</span>
)}
</td>
<td className="px-4 py-3 font-mono text-xs text-muted-foreground">
{doc.hash.slice(0, 8)}
</td>
<td className="px-4 py-3 text-xs text-muted-foreground">
{new Date(doc.updated_at).toLocaleString()}
</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" onClick={() => handleReindex(doc)} className="gap-1">
<RotateCw className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setDeleteTarget(doc)}
className="gap-1 text-destructive hover:text-destructive"
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
page={page}
pageSize={pageSize}
total={total}
totalPages={totalPages}
onPageChange={setPage}
onPageSizeChange={(size) => { setPageSize(size); setPage(1); }}
/>
</div>
)}
<MemoryDocumentsTable
documents={documents}
paginatedDocs={paginatedDocs}
loading={showSkeleton}
agentId={agentId}
agentWorkspace={selectedAgent?.workspace}
page={page}
pageSize={pageSize}
total={total}
totalPages={totalPages}
resolveContact={resolveContact}
agentMap={agentMap}
onViewDoc={setViewDoc}
onDeleteTarget={setDeleteTarget}
onReindex={handleReindex}
onPageChange={setPage}
onPageSizeChange={(size) => { setPageSize(size); setPage(1); }}
/>
</div>
{/* Dialogs */}