Files
SBrainCO/client/src/pages/StoreDetailPage.tsx
T
2026-07-26 22:48:08 +08:00

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>
)
}