import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useNavigate } from 'react-router-dom' import api from '@/lib/api' import { Badge } from '@/components/Badge' import { DataTable } from '@/components/DataTable' import { MetricCard } from '@/components/MetricCard' import { Pagination } from '@/components/Pagination' import { formatCurrency, formatPercent, riskColor } from '@/lib/utils' import { LoadingSpinner } from '@/components/LoadingSpinner' import { MonthPicker } from '@/components/MonthPicker' import { LineChart, Line, ResponsiveContainer, XAxis, YAxis, Tooltip } from 'recharts' import { useState, useMemo } from 'react' const PAGE_SIZE = 10 export function RegionalPage() { const navigate = useNavigate() const queryClient = useQueryClient() const [riskFilter, setRiskFilter] = useState('') const [month, setMonth] = useState('2026-05') const [riskPage, setRiskPage] = useState(1) const [checkPage, setCheckPage] = useState(1) const [benchPage, setBenchPage] = useState(1) const [weeklyModal, setWeeklyModal] = useState<{ taskId: number; storeName: string } | null>(null) const [weeklyText, setWeeklyText] = useState('') const { data: riskData, isLoading: riskLoading } = useQuery({ queryKey: ['stores/risk'], queryFn: () => api.get('/stores/risk'), }) const { data: weeklyData, isLoading: weeklyLoading } = useQuery({ queryKey: ['tasks/weekly-check'], queryFn: () => api.get('/tasks/weekly-check', { params: { month } }), }) const { data: priorityData, isLoading: priorityLoading } = useQuery({ queryKey: ['stores/priority'], queryFn: () => api.get('/stores/priority'), }) const { data: benchmarkData } = useQuery({ queryKey: ['benchmark/composite'], queryFn: () => api.get('/benchmark/composite'), }) const { data: regionData } = useQuery({ queryKey: ['region/summary'], queryFn: () => api.get('/region/summary'), }) const riskRows = ((riskData as any)?.data || []).filter((r: any) => !riskFilter || r.risk_level === riskFilter) const weeklyRows = (weeklyData as any)?.data || [] const priorityRows = (priorityData as any)?.data || [] const benchmarkRows = (benchmarkData as any)?.data || [] const regionRows = (regionData as any)?.data || [] const p0p1 = priorityRows.filter((s: any) => s.action_priority?.startsWith('P0') || s.action_priority?.startsWith('P1')) const needCheck = weeklyRows.filter((w: any) => parseInt(w.consecutive_no_improve_weeks) >= 2) const pagedRiskRows = useMemo(() => riskRows.slice((riskPage - 1) * PAGE_SIZE, riskPage * PAGE_SIZE), [riskRows, riskPage]) const pagedNeedCheck = useMemo(() => needCheck.slice((checkPage - 1) * PAGE_SIZE, checkPage * PAGE_SIZE), [needCheck, checkPage]) const pagedBenchmark = useMemo(() => benchmarkRows.slice((benchPage - 1) * PAGE_SIZE, benchPage * PAGE_SIZE), [benchmarkRows, benchPage]) const pageLoading = riskLoading || weeklyLoading || priorityLoading const weeklyMutation = useMutation({ mutationFn: ({ id, text }: { id: number; text: string }) => api.put(`/tasks/${id}/weekly-check`, { weekly_comment: text }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks/weekly-check'] }) setWeeklyModal(null) setWeeklyText('') }, }) if (pageLoading) { return } return (

区域经理工作台

{/* 汇总卡片 */}
r.risk_level !== '绿色').length} /> !w.last_check_date).length} />
{/* 区域汇总 */} {regionRows.length > 0 && (

区域经营汇总

formatCurrency(r.total_received) }, { key: 'avg_bill_value', label: '客单价', align: 'right', render: (r) => formatCurrency(r.avg_bill_value) }, { key: 'avg_discount_rate', label: '优惠率', align: 'right', render: (r) => formatPercent(r.avg_discount_rate) }, { key: 'avg_margin_rate', label: '毛利率', align: 'right', render: (r) => formatPercent(r.avg_margin_rate) }, { key: 'avg_anomaly_rate', label: '异常率', align: 'right', render: (r) => formatPercent(r.avg_anomaly_rate) }, { key: 'red_count', label: '红', align: 'center', render: (r) => 0 ? 'font-bold text-red-600' : ''}>{r.red_count} }, { key: 'yellow_count', label: '黄', align: 'center', render: (r) => 0 ? 'font-bold text-yellow-600' : ''}>{r.yellow_count} }, { key: 'green_count', label: '绿', align: 'center', render: (r) => {r.green_count} }, ]} data={regionRows} />
)} {/* 风险筛选 */}
{['', '红色', '黄色', '绿色'].map((r) => ( ))}
{/* 门店红黄绿列表 */}

门店风险分级 ({riskRows.length})

}, { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => formatPercent(r.discount_rate_pct) }, { key: 'theoretical_margin_pct', label: '毛利率', align: 'right', render: (r) => formatPercent(r.theoretical_margin_pct) }, { key: 'anomaly_rate_pct', label: '异常率', align: 'right', render: (r) => formatPercent(r.anomaly_rate_pct) }, ]} data={pagedRiskRows} onRowClick={(r) => navigate(`/stores/${r.store_code}`)} />
{/* 需到店检查门店 + 周检录入 */} {needCheck.length > 0 && (

需到店检查门店(连续2周未改善)({needCheck.length})

}, { key: 'action', label: '操作', render: (r) => ( ) }, ]} data={pagedNeedCheck} onRowClick={(r) => navigate(`/tasks/${r.task_id}`)} />
)} {/* 同类门店基准对比 */} {benchmarkRows.length > 0 && (

同类门店基准对比

{ const v = Number(r.benchmark_score || 0) return = 80 ? 'text-green-600' : v >= 60 ? 'text-yellow-600' : 'text-red-600'}>{v.toFixed(1)} } }, { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, { key: 'avg_bill_value', label: '客单价', align: 'right', render: (r) => formatCurrency(r.avg_bill_value) }, { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => formatPercent(r.discount_rate_pct) }, { key: 'theoretical_margin_pct', label: '毛利率', align: 'right', render: (r) => formatPercent(r.theoretical_margin_pct) }, { key: 'member_bill_share_pct', label: '会员占比', align: 'right', render: (r) => formatPercent(r.member_bill_share_pct) }, ]} data={pagedBenchmark} onRowClick={(r) => navigate(`/stores/${r.store_code}`)} />
)} {/* 周检录入弹窗 */} {weeklyModal && (
setWeeklyModal(null)}>
e.stopPropagation()}>

周检录入 - {weeklyModal.storeName}