风险与内控管理优化: 异常账单服务端分页/筛选/收银员搜索/合计, 收银员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 { MetricCard } from '@/components/MetricCard'
|
||||
import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils'
|
||||
@@ -189,7 +188,7 @@ export function LossDiagnosisTab({ month }: { month: string }) {
|
||||
const [page, setPage] = useState(1)
|
||||
const [sort, setSort] = useState('actual_store_contribution')
|
||||
const [filter, setFilter] = useState('')
|
||||
const [order, setOrder] = useState('asc')
|
||||
const [order, setOrder] = useState<'asc' | 'desc'>('asc')
|
||||
const [selectedStore, setSelectedStore] = useState<StoreDetail | null>(null)
|
||||
const { data, isLoading } = useQuery({ queryKey: ['se/loss-diagnosis', month, page, sort, filter, order], queryFn: () => api.get(`/store-expense/loss-diagnosis?page=${page}&page_size=${PAGE_SIZE}&sort=${sort}&filter=${filter}&order=${order}&month=${month}`) })
|
||||
|
||||
@@ -212,32 +211,36 @@ export function LossDiagnosisTab({ month }: { month: string }) {
|
||||
</div>
|
||||
|
||||
<CollapsibleSection title="亏损门店诊断" subtitle="点击门店名称查看详情">
|
||||
<div className="flex gap-2 mb-3 flex-wrap">
|
||||
{[
|
||||
<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={[
|
||||
{ key: '', label: '全部' },
|
||||
{ key: 'P0', label: 'P0 紧急' },
|
||||
{ key: 'P1', label: 'P1 整改' },
|
||||
{ key: 'P2', label: 'P2 观察' },
|
||||
].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>
|
||||
{[
|
||||
]}
|
||||
sortOptions={[
|
||||
{ key: 'actual_store_contribution', label: '贡献利润' },
|
||||
{ key: 'actual_store_contribution_rate_pct', label: '贡献率' },
|
||||
{ key: 'received', label: '实收' },
|
||||
{ key: 'wage_rate_pct', label: '人工率' },
|
||||
{ key: 'rent_rate_pct', label: '租金率' },
|
||||
{ key: 'received_per_sqm', label: '坪效' },
|
||||
].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>
|
||||
</div>
|
||||
<DataTable
|
||||
]}
|
||||
defaultSort="actual_store_contribution"
|
||||
defaultOrder="asc"
|
||||
onRowClick={(r) => setSelectedStore(r as StoreDetail)}
|
||||
columns={[
|
||||
{ key: 'sales_store_name', label: '门店', render: (r) => <button className="text-blue-600 hover:underline font-medium" onClick={() => setSelectedStore(r as StoreDetail)}>{r.sales_store_name}</button> },
|
||||
{ key: 'priority', label: '优先级', render: (r) => <span className={`px-2 py-0.5 rounded text-xs font-medium ${PRIORITY_COLORS[r.priority] || ''}`}>{r.priority}</span> },
|
||||
@@ -251,9 +254,7 @@ export function LossDiagnosisTab({ month }: { month: string }) {
|
||||
{ key: 'diagnosis_reasons', label: '亏损原因', render: (r) => <span className="text-xs">{r.diagnosis_reasons}</span> },
|
||||
{ key: 'suggested_actions', label: '建议动作', render: (r) => <span className="text-xs font-medium">{r.suggested_actions}</span> },
|
||||
]}
|
||||
data={rows}
|
||||
/>
|
||||
<Pagination page={page} pageSize={meta.pageSize} total={meta.total} onPageChange={setPage} />
|
||||
</CollapsibleSection>
|
||||
|
||||
<StoreDetailModal store={selectedStore} onClose={() => setSelectedStore(null)} />
|
||||
|
||||
Reference in New Issue
Block a user