104 lines
4.5 KiB
TypeScript
104 lines
4.5 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { useParams, useNavigate } from 'react-router-dom'
|
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
|
|
import api from '@/lib/api'
|
|
import { MetricCard } from '@/components/MetricCard'
|
|
import { Badge } from '@/components/Badge'
|
|
import { DataTable } from '@/components/DataTable'
|
|
import { formatCurrency, formatPercent, formatNumber } from '@/lib/utils'
|
|
import { ArrowLeft } from 'lucide-react'
|
|
|
|
export function StoreDetailPage() {
|
|
const { code } = useParams()
|
|
const navigate = useNavigate()
|
|
|
|
const { data: storeData } = useQuery({
|
|
queryKey: ['store', code],
|
|
queryFn: () => api.get(`/stores/${code}`),
|
|
})
|
|
|
|
const { data: dailyData } = useQuery({
|
|
queryKey: ['store', code, 'daily'],
|
|
queryFn: () => api.get(`/stores/${code}/daily`),
|
|
})
|
|
|
|
const { data: tasksData } = useQuery({
|
|
queryKey: ['store', code, 'tasks'],
|
|
queryFn: () => api.get('/tasks', { params: { store_code: code, month: '2026-05', page_size: 50 } }),
|
|
})
|
|
|
|
const sd = (storeData as any)?.data
|
|
const daily = ((dailyData as any)?.data) || []
|
|
const tasks = ((tasksData as any)?.data) || []
|
|
|
|
if (!sd) return <div>加载中...</div>
|
|
const sc = sd.scorecard
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<button onClick={() => navigate(-1)} className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">
|
|
<ArrowLeft size={16} /> 返回
|
|
</button>
|
|
|
|
<div className="flex items-center gap-3">
|
|
<h1 className="text-xl font-bold">{sc.store_name}</h1>
|
|
{sd.risk && <Badge type="risk" text={sd.risk.risk_level} />}
|
|
</div>
|
|
|
|
{/* 核心指标 */}
|
|
<div className="grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-6">
|
|
<MetricCard title="实收" value={sc.received} format="currency" />
|
|
<MetricCard title="账单数" value={sc.bill_count} format="number" />
|
|
<MetricCard title="客单价" value={sc.avg_bill_value} format="currency" />
|
|
<MetricCard title="优惠率" value={sc.discount_rate_pct} format="percent" />
|
|
<MetricCard title="理论毛利率" value={sc.theoretical_margin_pct} format="percent" />
|
|
<MetricCard title="会员占比" value={sc.member_bill_share_pct} format="percent" />
|
|
</div>
|
|
|
|
{/* 日度趋势 */}
|
|
<div className="rounded-lg border bg-card p-4">
|
|
<h2 className="mb-3 text-sm font-bold">日度实收趋势</h2>
|
|
<ResponsiveContainer width="100%" height={250}>
|
|
<LineChart data={daily}>
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|
<XAxis dataKey="business_date" tick={{ fontSize: 11 }} />
|
|
<YAxis tick={{ fontSize: 11 }} />
|
|
<Tooltip />
|
|
<Line type="monotone" dataKey="received" stroke="#3b82f6" name="实收" />
|
|
<Line type="monotone" dataKey="avg_bill_value" stroke="#eab308" name="客单价" />
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
|
|
{/* 平台经济性 */}
|
|
{sd.platform && (
|
|
<div className="rounded-lg border bg-card p-4">
|
|
<h2 className="mb-3 text-sm font-bold">平台经济性</h2>
|
|
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
|
|
<MetricCard title="美团实收" value={sd.platform.meituan_received} format="currency" />
|
|
<MetricCard title="饿了么实收" value={sd.platform.eleme_received} format="currency" />
|
|
<MetricCard title="抖音实收" value={sd.platform.douyin_received} format="currency" />
|
|
<MetricCard title="平台加权成本率" value={sd.platform.weighted_cost_rate_pct} format="percent" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 任务列表 */}
|
|
<div className="rounded-lg border bg-card p-4">
|
|
<h2 className="mb-3 text-sm font-bold">门店任务 ({tasks.length})</h2>
|
|
<DataTable
|
|
columns={[
|
|
{ key: 'problem_indicator', label: '问题指标' },
|
|
{ key: 'priority', label: '优先级', render: (r) => <Badge type="priority" text={r.priority} /> },
|
|
{ key: 'action_required', label: '行动要求', render: (r) => <span className="text-xs">{r.action_required}</span> },
|
|
{ key: 'status', label: '状态', render: (r) => <Badge type="status" text={r.status} /> },
|
|
{ key: 'deadline', label: '截止日', render: (r) => r.deadline?.substring(0, 10) },
|
|
]}
|
|
data={tasks}
|
|
onRowClick={(r) => navigate(`/tasks/${r.task_id}`)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|