From 68f291d76cbde86992e22cbd73ccb265c0ba0b74 Mon Sep 17 00:00:00 2001 From: NVWA Code Agent Date: Thu, 11 Dec 2025 16:45:14 +0000 Subject: [PATCH] application [cat-admin-web] view page [pages/dashboard] development --- apps/cat-admin-web/src/pages/dashboard.tsx | 250 +++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 apps/cat-admin-web/src/pages/dashboard.tsx diff --git a/apps/cat-admin-web/src/pages/dashboard.tsx b/apps/cat-admin-web/src/pages/dashboard.tsx new file mode 100644 index 0000000..34cd8f6 --- /dev/null +++ b/apps/cat-admin-web/src/pages/dashboard.tsx @@ -0,0 +1,250 @@ +import { useEffect, useState } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { entities } from "@/lib/nvwa"; + +interface CatInventoryStats { + totalCats: number; + availableCats: number; + gradeDistribution: { [key: string]: number }; +} + +interface ReservationStats { + totalReservations: number; + statusDistribution: { [key: string]: number }; + recentReservations: number; // 最近7天 +} + +interface QueueStatusSummary { + queuing: number; + reserved: number; + completed: number; + cancelled: number; +} + +export default function DashboardPage() { + const [catStats, setCatStats] = useState({ + totalCats: 0, + availableCats: 0, + gradeDistribution: {} + }); + + const [reservationStats, setReservationStats] = useState({ + totalReservations: 0, + statusDistribution: {}, + recentReservations: 0 + }); + + const [queueStats, setQueueStats] = useState({ + queuing: 0, + reserved: 0, + completed: 0, + cancelled: 0 + }); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + loadDashboardData(); + }, []); + + const loadDashboardData = async () => { + try { + setLoading(true); + + // 获取猫咪库存数据 + const { data: cats } = await entities.from("cat").select("id, is_available, grade"); + if (cats) { + const totalCats = cats.length; + const availableCats = cats.filter(cat => cat.is_available).length; + const gradeDistribution = cats.reduce((acc, cat) => { + acc[cat.grade] = (acc[cat.grade] || 0) + 1; + return acc; + }, {} as { [key: string]: number }); + + setCatStats({ totalCats, availableCats, gradeDistribution }); + } + + // 获取预约数据 + const { data: reservations } = await entities.from("reservation").select("id, status, created_at"); + if (reservations) { + const totalReservations = reservations.length; + const statusDistribution = reservations.reduce((acc, res) => { + acc[res.status] = (acc[res.status] || 0) + 1; + return acc; + }, {} as { [key: string]: number }); + + // 计算最近7天的预约 + const sevenDaysAgo = new Date(); + sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); + const recentReservations = reservations.filter(res => + new Date(res.created_at) >= sevenDaysAgo + ).length; + + setReservationStats({ totalReservations, statusDistribution, recentReservations }); + + // 设置排队状态摘要 + setQueueStats({ + queuing: statusDistribution.queuing || 0, + reserved: statusDistribution.reserved || 0, + completed: statusDistribution.completed || 0, + cancelled: statusDistribution.cancelled || 0 + }); + } + + } catch (error) { + console.error("Failed to load dashboard data:", error); + } finally { + setLoading(false); + } + }; + + if (loading) { + return ( +
+
+
+

加载中...

+
+
+ ); + } + + return ( +
+
+

仪表板

+

猫舍管理概览

+
+ + {/* 猫咪库存概览 */} +
+ + + 总猫咪数量 + + +
{catStats.totalCats}
+

庇护所中的猫咪总数

+
+
+ + + + 可售卖猫咪 + + +
{catStats.availableCats}
+

当前可预约的猫咪数量

+
+
+ + + + 等级分布 + + +
+ {Object.entries(catStats.gradeDistribution).map(([grade, count]) => ( +
+ + {grade}级 + + {count} +
+ ))} +
+
+
+ + + + 可售卖比例 + + +
+ {catStats.totalCats > 0 ? Math.round((catStats.availableCats / catStats.totalCats) * 100) : 0}% +
+

可售卖猫咪占总数比例

+
+
+
+ + {/* 预约统计 */} +
+ + + 预约统计 + 总体预约情况概览 + + +
+
+ 总预约数 + {reservationStats.totalReservations} +
+
+ 最近7天 + {reservationStats.recentReservations} +
+
+
+
+ + + + 预约状态分布 + 不同状态的预约数量 + + +
+ {Object.entries(reservationStats.statusDistribution).map(([status, count]) => ( +
+ + {status === 'queuing' ? '排队中' : + status === 'reserved' ? '已预约' : + status === 'completed' ? '已完成' : + status === 'cancelled' ? '已取消' : status} + + {count} +
+ ))} +
+
+
+ + + + 排队状态摘要 + 当前排队和预约状态 + + +
+
+ 排队中 + {queueStats.queuing} +
+
+ 已预约 + {queueStats.reserved} +
+
+ 已完成 + {queueStats.completed} +
+
+ 已取消 + {queueStats.cancelled} +
+
+
+
+
+
+ ); +} \ No newline at end of file