EventBuzz_Console/src/app/components/dashboard/StatsBar.tsx

120 lines
4.2 KiB
TypeScript

import React, { useMemo } from "react";
import { Users, CalendarDays, BarChart2, Trophy } from "lucide-react";
import { useBreakpoint } from "../../hooks/useBreakpoint";
function fmt(n: number | undefined): string {
if (n == null || Number.isNaN(n)) return "—";
return n.toLocaleString();
}
export function StatsBar({
loading,
participantCount,
sessionCount,
quizPollCount,
pointsAwarded,
statusCounts,
upcomingSessionCount,
pointsRedeemed,
}: {
loading?: boolean;
participantCount?: number;
sessionCount?: number;
quizPollCount?: number;
pointsAwarded?: number;
pointsRedeemed?: number;
statusCounts?: Record<string, number>;
upcomingSessionCount?: number;
}) {
const bp = useBreakpoint();
const cols = bp === "mobile" ? 2 : 4;
const participantTrend = useMemo(() => {
if (loading) return undefined;
const ci = statusCounts?.checked_in ?? 0;
const conf = statusCounts?.confirmed ?? 0;
if (ci) return `${ci.toLocaleString()} checked in`;
if (conf) return `${conf.toLocaleString()} confirmed`;
return undefined;
}, [loading, statusCounts]);
const sessionTrend =
!loading && upcomingSessionCount != null && upcomingSessionCount > 0
? `${upcomingSessionCount} upcoming`
: undefined;
const pointsTrend =
!loading && pointsRedeemed != null && pointsRedeemed > 0
? `${pointsRedeemed.toLocaleString()} redeemed`
: undefined;
const stats = [
{
icon: <Users size={15} />,
label: "Participants",
value: loading ? "—" : fmt(participantCount),
trend: participantTrend,
trendUp: !!(statusCounts?.checked_in && statusCounts.checked_in > 0),
accent: "var(--chart-1)",
},
{
icon: <CalendarDays size={15} />,
label: "Sessions",
value: loading ? "—" : fmt(sessionCount),
trend: sessionTrend,
trendUp: false,
accent: "var(--chart-3)",
},
{
icon: <BarChart2 size={15} />,
label: "Quizzes & Polls",
value: loading ? "—" : fmt(quizPollCount),
trend: undefined,
trendUp: false,
accent: "var(--chart-4)",
},
{
icon: <Trophy size={15} />,
label: "Points Awarded",
value: loading ? "—" : fmt(pointsAwarded),
trend: pointsTrend,
trendUp: false,
accent: "var(--chart-2)",
},
];
return (
<div style={{ display: "grid", gridTemplateColumns: `repeat(${cols}, 1fr)`, gap: bp === "mobile" ? "10px" : "12px" }}>
{stats.map((stat, idx) => (
<div key={idx} style={{
background: "var(--card)",
borderTopWidth: "1px", borderRightWidth: "1px",
borderBottomWidth: "1px", borderLeftWidth: "3px",
borderTopStyle: "solid", borderRightStyle: "solid",
borderBottomStyle: "solid", borderLeftStyle: "solid",
borderTopColor: "var(--border)", borderRightColor: "var(--border)",
borderBottomColor: "var(--border)", borderLeftColor: stat.accent,
borderRadius: "var(--radius-card)", padding: bp === "mobile" ? "14px 14px" : "16px 20px",
display: "flex", alignItems: "flex-start", gap: "12px",
}}>
<div style={{ width: "30px", height: "30px", borderRadius: "var(--radius-button)", background: stat.accent + "15", display: "flex", alignItems: "center", justifyContent: "center", color: stat.accent, flexShrink: 0, marginTop: "2px" }}>
{stat.icon}
</div>
<div>
<p style={{ fontSize: bp === "mobile" ? "var(--text-xl)" : "var(--text-2xl)", fontFamily: "var(--font-inter)", fontWeight: "var(--font-weight-medium)", color: "var(--foreground)", lineHeight: "1.1" }}>
{stat.value}
</p>
<p style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: "var(--muted)", marginTop: "3px", lineHeight: "1.3" }}>
{stat.label}
</p>
{stat.trend && (
<p style={{ fontSize: "var(--text-xs)", fontFamily: "var(--font-noto-sans)", color: stat.trendUp ? "#16a34a" : "var(--muted)", marginTop: "6px", lineHeight: "1.2" }}>
{stat.trend}
</p>
)}
</div>
</div>
))}
</div>);
}