风险与内控管理优化: 异常账单服务端分页/筛选/收银员搜索/合计, 收银员TOP15按异常账单数排序, 双击跳转明细, MonthPicker靠右, 侧边栏底部间距
This commit is contained in:
@@ -2,8 +2,7 @@ import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import api from '@/lib/api'
|
||||
import { CollapsibleSection } from '@/components/CollapsibleSection'
|
||||
import { DataTable } from '@/components/DataTable'
|
||||
import { Pagination } from '@/components/Pagination'
|
||||
import { FilterableTable } from '@/components/FilterableTable'
|
||||
import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||||
import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils'
|
||||
|
||||
@@ -22,7 +21,7 @@ export function RentRiskTab({ month }: { month: string }) {
|
||||
const [sort, setSort] = useState('rent_rate_pct')
|
||||
const [filter, setFilter] = useState('')
|
||||
const [riskOnly, setRiskOnly] = useState(false)
|
||||
const [order, setOrder] = useState('desc')
|
||||
const [order, setOrder] = useState<'asc' | 'desc'>('desc')
|
||||
const { data, isLoading } = useQuery({ queryKey: ['se/rent', month, page, sort, filter, riskOnly, order], queryFn: () => api.get(`/store-expense/rent-risk?page=${page}&page_size=${PAGE_SIZE}&sort=${sort}&filter=${filter}&risk_only=${riskOnly}&order=${order}&month=${month}`) })
|
||||
|
||||
if (isLoading) return <LoadingSpinner text="加载房租及租约风险..." />
|
||||
@@ -49,23 +48,27 @@ export function RentRiskTab({ month }: { month: string }) {
|
||||
<div className="space-y-4">
|
||||
<CollapsibleSection title="房租及租约风险" subtitle="租金占比 + 租约到期日,识别续租和关停风险门店">
|
||||
<div className="flex gap-2 mb-3 flex-wrap">
|
||||
{filters.map(f => (
|
||||
<button key={f.key} onClick={() => { setFilter(f.key); setPage(1) }} className={`px-3 py-1 rounded text-xs ${filter === f.key ? 'bg-primary text-primary-foreground' : 'bg-muted'}`}>{f.label}</button>
|
||||
))}
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
{sorts.map(s => (
|
||||
<button key={s.key} onClick={() => { setSort(s.key); setPage(1) }} className={`px-3 py-1 rounded text-xs ${sort === s.key ? 'bg-primary text-primary-foreground' : 'bg-muted'}`}>按{s.label}</button>
|
||||
))}
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
<button onClick={() => { setOrder(order === 'asc' ? 'desc' : 'asc'); setPage(1) }} className="px-3 py-1 rounded text-xs bg-muted">
|
||||
{order === 'asc' ? '↑ 升序' : '↓ 降序'}
|
||||
</button>
|
||||
<span className="mx-2 text-muted-foreground">|</span>
|
||||
<button onClick={() => { setRiskOnly(!riskOnly); setPage(1) }} className={`px-3 py-1 rounded text-xs ${riskOnly ? 'bg-primary text-primary-foreground' : 'bg-muted'}`}>
|
||||
{riskOnly ? '显示全部' : '仅显示风险门店'}
|
||||
</button>
|
||||
</div>
|
||||
<DataTable
|
||||
<FilterableTable
|
||||
data={rows}
|
||||
serverSide
|
||||
page={page}
|
||||
onPageChange={setPage}
|
||||
sort={sort}
|
||||
onSortChange={setSort}
|
||||
order={order}
|
||||
onOrderChange={setOrder}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={meta.total}
|
||||
filter={filter}
|
||||
onFilterChange={setFilter}
|
||||
filterButtons={filters}
|
||||
sortOptions={sorts}
|
||||
defaultSort="rent_rate_pct"
|
||||
defaultOrder="desc"
|
||||
columns={[
|
||||
{ key: 'sales_store_name', label: '门店' },
|
||||
{ key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) },
|
||||
@@ -77,9 +80,7 @@ export function RentRiskTab({ month }: { month: string }) {
|
||||
{ key: 'risk_level', label: '风险等级', render: (r) => <span className={RISK_COLORS[r.risk_level] || ''}>{r.risk_level}</span> },
|
||||
{ key: 'suggestion', label: '建议' },
|
||||
]}
|
||||
data={rows}
|
||||
/>
|
||||
<Pagination page={page} pageSize={meta.pageSize} total={meta.total} onPageChange={setPage} />
|
||||
</CollapsibleSection>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user