初始化:连锁餐饮数字化运营管理平台

This commit is contained in:
freedakgmail
2026-07-26 22:48:08 +08:00
commit a7874d79b5
67 changed files with 14391 additions and 0 deletions
+124
View File
@@ -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>
)
}