Skip to content

Commit 58e2837

Browse files
committed
Implement organization statistics fetching and display in GraphicsPage
1 parent c39867e commit 58e2837

File tree

2 files changed

+162
-70
lines changed

2 files changed

+162
-70
lines changed

app/[orgId]/page.tsx

+61-70
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,72 @@
11
"use client";
2-
import Charts from "@/components/statstable";
2+
import { useContext } from "react";
3+
import { AuthContext } from "@/contexts/auth-context";
4+
import { useOrgStats } from "@/hooks/use-org-stats";
5+
import { useParams } from "next/navigation";
6+
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
7+
import { AlertCircle } from "lucide-react";
8+
import { Skeleton } from "@/components/ui/skeleton";
9+
import StatCard from "@/components/stat-card";
310

4-
interface ChartData {
5-
submissions: {
6-
total: number;
7-
weeklyData: Array<{
8-
date: string;
9-
steps: number;
10-
}>;
11-
weeklyTotal: number;
12-
};
13-
upcomingContest: {
14-
count: number;
15-
variability: number;
16-
weeklyData: Array<{
17-
date: string;
18-
resting: number;
19-
}>;
20-
};
21-
contestDetails: {
22-
currentYear: {
23-
year: string;
24-
contestsPerDay: number;
25-
};
26-
previousYear: {
27-
year: string;
28-
contestsPerDay: number;
29-
};
30-
};
11+
function StatsLoadingSkeleton() {
12+
return (
13+
<div className="container mx-auto max-w-5xl py-6">
14+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
15+
{[1, 2, 3, 4].map((i) => (
16+
<div key={i} className="p-4 border rounded-lg">
17+
<Skeleton className="h-4 w-24 mb-2" />
18+
<Skeleton className="h-8 w-16" />
19+
</div>
20+
))}
21+
</div>
22+
</div>
23+
);
3124
}
3225

3326
export default function GraphicsPage() {
34-
const chartData: ChartData = {
35-
submissions: {
36-
total: 12584,
37-
weeklyData: [
38-
{ date: "2024-01-01", steps: 2000 },
39-
{ date: "2024-01-02", steps: 2100 },
40-
{ date: "2024-01-03", steps: 2200 },
41-
{ date: "2024-01-04", steps: 2300 },
42-
{ date: "2024-01-05", steps: 2400 },
43-
{ date: "2024-01-06", steps: 2500 },
44-
{ date: "2024-01-07", steps: 2600 },
45-
// ... more data
46-
],
47-
weeklyTotal: 5305,
48-
},
49-
upcomingContest: {
50-
count: 62,
51-
variability: 35,
52-
weeklyData: [
53-
{ date: "2024-01-01", resting: 62 },
54-
{ date: "2024-01-02", resting: 63 },
55-
{ date: "2024-01-03", resting: 64 },
56-
{ date: "2024-01-04", resting: 65 },
57-
{ date: "2024-01-05", resting: 66 },
58-
{ date: "2024-01-06", resting: 67 },
59-
{ date: "2024-01-07", resting: 68 },
27+
const { user } = useContext(AuthContext);
28+
const params = useParams();
29+
const orgId = params.orgId as string;
30+
31+
const currentOrg = user?.orgs.find((org) => org.nameId === orgId);
32+
const { stats, loading, error } = useOrgStats(orgId, currentOrg?.role);
6033

61-
// ... more data
62-
],
63-
},
64-
contestDetails: {
65-
currentYear: {
66-
year: "2024",
67-
contestsPerDay: 12453,
68-
},
69-
previousYear: {
70-
year: "2023",
71-
contestsPerDay: 10103,
72-
},
73-
},
74-
};
34+
if (loading) return <StatsLoadingSkeleton />;
35+
if (error)
36+
return (
37+
<Alert variant="destructive" className="max-w-lg mx-auto my-4">
38+
<AlertCircle className="h-4 w-4" />
39+
<AlertTitle>Error</AlertTitle>
40+
<AlertDescription>{error}</AlertDescription>
41+
</Alert>
42+
);
7543

7644
return (
77-
<div>
78-
<Charts data={chartData} />
45+
<div className="container mx-auto max-w-5xl py-6">
46+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
47+
<StatCard
48+
heading="Total Contests"
49+
value={stats.totalContests}
50+
unit="contests"
51+
/>
52+
<StatCard
53+
heading="Upcoming Contests"
54+
value={stats.upcomingContests}
55+
unit="contests"
56+
/>
57+
<StatCard
58+
heading="Ended Contests"
59+
value={stats.endedContests}
60+
unit="contests"
61+
/>
62+
{currentOrg?.role === "owner" && (
63+
<StatCard
64+
heading="Total Members"
65+
value={stats.totalMembers}
66+
unit="members"
67+
/>
68+
)}
69+
</div>
7970
</div>
8071
);
8172
}

hooks/use-org-stats.ts

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useEffect, useState } from "react";
2+
import { fetchApi } from "@/lib/client/fetch";
3+
4+
type Period = "day" | "week" | "month" | "year";
5+
6+
interface StatsResponse {
7+
value: number;
8+
}
9+
10+
interface StatConfig {
11+
key: keyof typeof defaultStats;
12+
stat: string;
13+
period?: Period;
14+
roleRequired?: string[];
15+
}
16+
17+
const defaultStats = {
18+
totalContests: 0,
19+
upcomingContests: 0,
20+
endedContests: 0,
21+
totalMembers: 0,
22+
totalProblems: 0,
23+
totalGroups: 0,
24+
};
25+
26+
const statsConfig: StatConfig[] = [
27+
{
28+
key: "totalContests",
29+
stat: "total-contests",
30+
roleRequired: ["owner", "organizer", "member"],
31+
},
32+
{
33+
key: "upcomingContests",
34+
stat: "upcoming-contests",
35+
roleRequired: ["owner", "organizer", "member"],
36+
},
37+
{
38+
key: "endedContests",
39+
stat: "ended-contests",
40+
roleRequired: ["owner", "organizer", "member"],
41+
},
42+
{
43+
key: "totalMembers",
44+
stat: "total-members",
45+
roleRequired: ["owner"],
46+
},
47+
{
48+
key: "totalProblems",
49+
stat: "total-problems",
50+
roleRequired: ["owner", "organizer", "member"],
51+
},
52+
{
53+
key: "totalGroups",
54+
stat: "total-groups",
55+
roleRequired: ["owner", "organizer", "member"],
56+
},
57+
];
58+
59+
export function useOrgStats(orgId: string, currentRole?: string) {
60+
const [stats, setStats] = useState(defaultStats);
61+
const [loading, setLoading] = useState(true);
62+
const [error, setError] = useState<string | null>(null);
63+
64+
useEffect(() => {
65+
const fetchStats = async () => {
66+
try {
67+
// Filter stats based on user's role
68+
const allowedStats = statsConfig.filter((config) =>
69+
config.roleRequired?.includes(currentRole || ""),
70+
);
71+
72+
// Create array of promises for allowed stats
73+
const responses = await Promise.all(
74+
allowedStats.map(({ stat, period }) =>
75+
fetchApi<StatsResponse>(
76+
`/orgs/${orgId}/stats?stat=${stat}${period ? `&period=${period}` : ""}`,
77+
),
78+
),
79+
);
80+
81+
// Combine responses into stats object
82+
const newStats = { ...defaultStats };
83+
allowedStats.forEach(({ key }, index) => {
84+
newStats[key] = responses[index].value;
85+
});
86+
87+
setStats(newStats);
88+
} catch (err) {
89+
setError(err instanceof Error ? err.message : "Failed to fetch stats");
90+
} finally {
91+
setLoading(false);
92+
}
93+
};
94+
95+
if (orgId) {
96+
fetchStats();
97+
}
98+
}, [orgId, currentRole]);
99+
100+
return { stats, loading, error };
101+
}

0 commit comments

Comments
 (0)