fix: 成本分析Tab改用FilterableTable带分页筛选排序,提取共享组件

This commit is contained in:
freedakgmail
2026-07-30 09:12:12 +08:00
parent a6d83be0ad
commit 3727cf8672
3 changed files with 97 additions and 81 deletions
+83
View File
@@ -0,0 +1,83 @@
import { useState, useMemo } from 'react'
import { DataTable } from '@/components/DataTable'
import { Pagination } from '@/components/Pagination'
import type { ReactNode } from 'react'
const PAGE_SIZE = 20
interface FilterableTableProps {
data: any[]
columns: { key: string; label: string; align?: 'left' | 'right' | 'center'; render?: (row: any) => ReactNode }[]
onRowClick?: (row: any) => void
filterKey?: string
filterLabel?: string
sortOptions: { key: string; label: string }[]
defaultSort?: string
defaultOrder?: 'asc' | 'desc'
statusFilterKey?: string
statusFilterLabel?: string
statusOptions?: { value: string; label: string }[]
}
export function FilterableTable({
data, columns, onRowClick,
filterKey, filterLabel,
sortOptions, defaultSort, defaultOrder = 'desc',
statusFilterKey, statusFilterLabel, statusOptions,
}: FilterableTableProps) {
const [page, setPage] = useState(1)
const [filter, setFilter] = useState('')
const [sort, setSort] = useState(defaultSort || sortOptions[0]?.key || '')
const [order, setOrder] = useState<'asc' | 'desc'>(defaultOrder)
const [statusFilter, setStatusFilter] = useState('')
const filterValues = useMemo(() => {
if (!filterKey) return []
return [...new Set(data.map((r: any) => r[filterKey]).filter(Boolean))].sort() as string[]
}, [data, filterKey])
const filtered = useMemo(() => {
let r = data
if (filter && filterKey) r = r.filter((row: any) => row[filterKey] === filter)
if (statusFilter && statusFilterKey) r = r.filter((row: any) => row[statusFilterKey] === statusFilter)
return [...r].sort((a: any, b: any) => {
const av = parseFloat(a[sort]) || (typeof a[sort] === 'string' ? 0 : a[sort] || 0)
const bv = parseFloat(b[sort]) || (typeof b[sort] === 'string' ? 0 : b[sort] || 0)
return order === 'desc' ? bv - av : av - bv
})
}, [data, filter, filterKey, sort, order, statusFilter, statusFilterKey])
const total = filtered.length
const paged = filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
return (
<>
<div className="mb-3 flex flex-wrap gap-2">
{filterKey && (
<select value={filter} onChange={(e) => { setFilter(e.target.value); setPage(1) }} className="rounded border px-2 py-1 text-sm">
<option value="">{filterLabel || '全部'}</option>
{filterValues.map((s) => <option key={s} value={s}>{s}</option>)}
</select>
)}
{statusFilterKey && statusOptions && (
<select value={statusFilter} onChange={(e) => { setStatusFilter(e.target.value); setPage(1) }} className="rounded border px-2 py-1 text-sm">
<option value="">{statusFilterLabel || '全部状态'}</option>
{statusOptions.map((s) => <option key={s.value} value={s.value}>{s.label}</option>)}
</select>
)}
<span className="mx-1 text-muted-foreground">|</span>
{sortOptions.map(s => (
<button key={s.key} onClick={() => { setSort(s.key); setPage(1) }} className={`rounded px-3 py-1 text-xs ${sort === s.key ? 'bg-primary text-primary-foreground' : 'bg-muted'}`}>
{s.label}
</button>
))}
<span className="mx-1 text-muted-foreground">|</span>
<button onClick={() => { setOrder(order === 'asc' ? 'desc' : 'asc'); setPage(1) }} className="rounded bg-muted px-3 py-1 text-xs">
{order === 'asc' ? '↑ 升序' : '↓ 降序'}
</button>
</div>
<DataTable columns={columns} data={paged} onRowClick={onRowClick} />
<Pagination page={page} pageSize={PAGE_SIZE} total={total} onPageChange={setPage} />
</>
)
}