feat(dashboard): add dashboard with statistics
- Add dashboard stats API endpoint with key metrics - Add stat-card component for displaying metrics - Add occupancy-chart component for court occupancy visualization - Add recent-bookings component for today's bookings list - Add quick-actions component for common admin actions - Update dashboard page with full implementation Stats include: today's bookings, revenue, occupancy rate, active members, pending bookings, and upcoming tournaments. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
134
apps/web/components/dashboard/stat-card.tsx
Normal file
134
apps/web/components/dashboard/stat-card.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon: ReactNode;
|
||||
trend?: {
|
||||
value: number;
|
||||
isPositive: boolean;
|
||||
};
|
||||
color?: "primary" | "accent" | "green" | "blue" | "purple" | "orange";
|
||||
}
|
||||
|
||||
const colorVariants = {
|
||||
primary: {
|
||||
bg: "bg-primary-50",
|
||||
icon: "bg-primary-100 text-primary-600",
|
||||
text: "text-primary-700",
|
||||
},
|
||||
accent: {
|
||||
bg: "bg-accent-50",
|
||||
icon: "bg-accent-100 text-accent-600",
|
||||
text: "text-accent-700",
|
||||
},
|
||||
green: {
|
||||
bg: "bg-green-50",
|
||||
icon: "bg-green-100 text-green-600",
|
||||
text: "text-green-700",
|
||||
},
|
||||
blue: {
|
||||
bg: "bg-blue-50",
|
||||
icon: "bg-blue-100 text-blue-600",
|
||||
text: "text-blue-700",
|
||||
},
|
||||
purple: {
|
||||
bg: "bg-purple-50",
|
||||
icon: "bg-purple-100 text-purple-600",
|
||||
text: "text-purple-700",
|
||||
},
|
||||
orange: {
|
||||
bg: "bg-orange-50",
|
||||
icon: "bg-orange-100 text-orange-600",
|
||||
text: "text-orange-700",
|
||||
},
|
||||
};
|
||||
|
||||
export function StatCard({ title, value, icon, trend, color = "primary" }: StatCardProps) {
|
||||
const colors = colorVariants[color];
|
||||
|
||||
return (
|
||||
<Card className="hover:shadow-md transition-shadow">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-primary-500 mb-1">{title}</p>
|
||||
<p className="text-3xl font-bold text-primary-800">{value}</p>
|
||||
{trend && (
|
||||
<div className="flex items-center mt-2">
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-medium flex items-center gap-1",
|
||||
trend.isPositive ? "text-green-600" : "text-red-600"
|
||||
)}
|
||||
>
|
||||
{trend.isPositive ? (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 10l7-7m0 0l7 7m-7-7v18"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 14l-7 7m0 0l-7-7m7 7V3"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{trend.isPositive ? "+" : ""}
|
||||
{trend.value}%
|
||||
</span>
|
||||
<span className="text-xs text-primary-400 ml-1">vs ayer</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex-shrink-0 w-12 h-12 rounded-lg flex items-center justify-center",
|
||||
colors.icon
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Loading skeleton for stat card
|
||||
export function StatCardSkeleton() {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-start justify-between animate-pulse">
|
||||
<div className="flex-1">
|
||||
<div className="h-4 bg-primary-100 rounded w-24 mb-2"></div>
|
||||
<div className="h-8 bg-primary-100 rounded w-16"></div>
|
||||
</div>
|
||||
<div className="w-12 h-12 bg-primary-100 rounded-lg"></div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user