import { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { MoreHorizontal, Pencil, Trash2, Plus } from 'lucide-react'; import { footprintDelete, footprintList, listTaxonomy, listTypes, statsOverview } from '@/lib/api'; import type { FootprintRow } from '@/lib/types'; import { Badge, Button, Confirm, Dropdown, DropdownItem, Empty, Input, Select, Table, TableWrap, Td, Th, Tr, useToast } from '@/components/ui'; import { StatusBadge, LevelBadge, RoleBadge, GradeBadge } from '@/components/common/badges'; import { RecordEditorDialog } from '@/components/records/RecordEditorDialog'; import { RecordDetailDialog } from '@/components/records/RecordDetailDialog'; import { fmtRange, fmtDateTime } from '@/lib/format'; const PAGE_SIZE = 20; export function RecordsPage() { const qc = useQueryClient(); const toast = useToast(); const [params, setParams] = useSearchParams(); const [q, setQ] = useState(params.get('q') ?? ''); const [typeId, setTypeId] = useState(''); const [year, setYear] = useState(''); const [level, setLevel] = useState(''); const [role, setRole] = useState(''); const [status, setStatus] = useState(''); const [sort, setSort] = useState('date_desc'); const [page, setPage] = useState(1); const [editingId, setEditingId] = useState(null); const [editorOpen, setEditorOpen] = useState(false); const [deleting, setDeleting] = useState(null); const [detailId, setDetailId] = useState(null); const [detailOpen, setDetailOpen] = useState(false); // 新建入口:/records?new=1 useEffect(() => { if (params.get('new') === '1') { setEditingId(null); setEditorOpen(true); params.delete('new'); setParams(params, { replace: true }); } }, [params, setParams]); const typesQ = useQuery({ queryKey: ['types'], queryFn: listTypes }); const taxQ = useQuery({ queryKey: ['taxonomy-all'], queryFn: () => listTaxonomy() }); const yearsQ = useQuery({ queryKey: ['stats', undefined], queryFn: () => statsOverview(undefined) }); const query = useMemo(() => ({ q: q || undefined, types: typeId ? [Number(typeId)] : undefined, levels: level ? [level] : undefined, roles: role ? [role] : undefined, statuses: status ? [status] : undefined, year: year ? Number(year) : undefined, sort, page, page_size: PAGE_SIZE }), [q, typeId, year, level, role, status, sort, page]); const listQ = useQuery({ queryKey: ['footprints', query], queryFn: () => footprintList(query) }); useEffect(() => { setPage(1); }, [q, typeId, year, level, role, status]); const delMut = useMutation({ mutationFn: (id: number) => footprintDelete(id), onSuccess: () => { toast('success', '记录已删除'); void qc.invalidateQueries({ queryKey: ['footprints'] }); void qc.invalidateQueries({ queryKey: ['timeline'] }); void qc.invalidateQueries({ queryKey: ['stats'] }); setDeleting(null); } }); const total = listQ.data?.total ?? 0; const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); const openNew = () => { setEditingId(null); setEditorOpen(true); }; const openEdit = (id: number) => { setEditingId(id); setEditorOpen(true); }; const openDetail = (id: number) => { setDetailId(id); setDetailOpen(true); }; return (
setQ(e.target.value)} />
setYear(e.target.value)} options={[{ value: '', label: '全部年份' }, ...(yearsQ.data?.years ?? []).map(y => ({ value: String(y), label: y + ' 年' }))]} /> setRole(e.target.value)} options={[{ value: '', label: '全部角色' }, ...(taxQ.data?.items ?? []).filter(t => t.kind === 'role').map(t => ({ value: t.value, label: t.value }))]} /> setSort(e.target.value)} options={[ { value: 'date_desc', label: '时间从新到旧' }, { value: 'date_asc', label: '时间从旧到新' }, { value: 'updated_desc', label: '最近修改' }, { value: 'weight_desc', label: '重要度优先' } ]} />
共 {total} 条
{(listQ.data?.items ?? []).map(row => ( openDetail(row.id)}> ))}
标题 类型 时间 级别 奖等 角色 状态 附件 更新时间
{row.title}
{row.type_name} {fmtRange(row.start_date, row.end_date)} {row.attachment_count > 0 ? row.attachment_count : ''} {fmtDateTime(row.updated_at)} e.stopPropagation()}> } > {close => ( <> { close(); openEdit(row.id); }}> 编辑 { close(); setDeleting(row); }}> 删除 )}
{!listQ.isLoading && !listQ.data?.items.length && ( 新建记录 } /> )}
{/* 手机端:卡片列表(表格在窄屏不可用) */}
{(listQ.data?.items ?? []).map(row => ( ))} {!listQ.isLoading && !listQ.data?.items.length && ( 新建记录} /> )}
{total > 0 && (
第 {page} / {totalPages} 页
)}
setDetailOpen(false)} footprintId={detailId} onEdit={id => { setDetailOpen(false); openEdit(id); }} /> setEditorOpen(false)} footprintId={editingId} /> setDeleting(null)} onOk={() => deleting && delMut.mutate(deleting.id)} loading={delMut.isPending} />
); }