初始化:连锁餐饮数字化运营管理平台
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import api from '@/lib/api'
|
||||
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts'
|
||||
import { DataTable } from '@/components/DataTable'
|
||||
import { Badge } from '@/components/Badge'
|
||||
import { MetricCard } from '@/components/MetricCard'
|
||||
import { Pagination } from '@/components/Pagination'
|
||||
import { useState, useMemo } from 'react'
|
||||
|
||||
const REVIEW_COLORS = { '达标': '#22c55e', '改善中': '#eab308', '未改善': '#ef4444' }
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
export function MonthlyReviewPage() {
|
||||
const [month, setMonth] = useState('2026-05')
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ['monthly-review', month],
|
||||
queryFn: () => api.get('/tasks/monthly-review', { params: { month } }),
|
||||
})
|
||||
|
||||
const rd = (data as any)?.data
|
||||
const summary = rd?.summary || { total: 0, passed: 0, improving: 0, failed: 0 }
|
||||
const details = rd?.details || []
|
||||
const pagedDetails = useMemo(() => details.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [details, page])
|
||||
|
||||
const pieData = [
|
||||
{ name: '达标', value: summary.passed },
|
||||
{ name: '改善中', value: summary.improving },
|
||||
{ name: '未改善', value: summary.failed },
|
||||
].filter((d) => d.value > 0)
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold">月度验收</h1>
|
||||
<input
|
||||
type="month"
|
||||
value={month}
|
||||
onChange={(e) => setMonth(e.target.value)}
|
||||
className="rounded-md border px-3 py-1.5 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 汇总卡片 */}
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
|
||||
<MetricCard title="总任务数" value={summary.total} />
|
||||
<MetricCard title="达标" value={summary.passed} />
|
||||
<MetricCard title="改善中" value={summary.improving} />
|
||||
<MetricCard title="未改善" value={summary.failed} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{/* 饼图 */}
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h2 className="mb-3 text-sm font-bold">验收结果分布</h2>
|
||||
{pieData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<PieChart>
|
||||
<Pie data={pieData} cx="50%" cy="50%" outerRadius={80} dataKey="value"
|
||||
label={({ name, value }: any) => `${name}: ${value}`}>
|
||||
{pieData.map((d) => <Cell key={d.name} fill={REVIEW_COLORS[d.name as keyof typeof REVIEW_COLORS]} />)}
|
||||
</Pie>
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
) : <div className="py-20 text-center text-muted-foreground">暂无验收数据</div>}
|
||||
</div>
|
||||
|
||||
{/* 经验标准化 */}
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h2 className="mb-3 text-sm font-bold">经验标准化</h2>
|
||||
<p className="text-sm text-muted-foreground mb-3">达标门店的有效经验可标准化推广</p>
|
||||
<PracticeSection />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 明细表 */}
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h2 className="mb-3 text-sm font-bold">验收明细</h2>
|
||||
<Pagination page={page} pageSize={PAGE_SIZE} total={details.length} onPageChange={setPage} />
|
||||
<div className="mt-3">
|
||||
<DataTable
|
||||
columns={[
|
||||
{ key: 'store_name', label: '门店' },
|
||||
{ key: 'problem_indicator', label: '问题指标' },
|
||||
{ key: 'baseline_value', label: '基线', align: 'right' },
|
||||
{ key: 'target_value', label: '目标', align: 'right' },
|
||||
{ key: 'actual_value', label: '实际', align: 'right' },
|
||||
{ key: 'review_result', label: '结果', render: (r) => <Badge type="review" text={r.review_result} /> },
|
||||
]}
|
||||
data={pagedDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PracticeSection() {
|
||||
const { data } = useQuery({
|
||||
queryKey: ['practices'],
|
||||
queryFn: () => api.get('/tasks/practices'),
|
||||
})
|
||||
|
||||
const practices = (data as any)?.data || []
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{practices.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">暂无标准化经验</p>
|
||||
) : practices.map((p: any) => (
|
||||
<div key={p.id} className="rounded-md border p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">{p.practice_module}</span>
|
||||
<Badge text={p.status} />
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">标杆:{p.benchmark_store_name}</p>
|
||||
<p className="mt-1 text-xs">{p.key_actions}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user