Skip to content

Commit 89f304b

Browse files
committed
feat: updated the org
1 parent edbfe1a commit 89f304b

6 files changed

Lines changed: 696 additions & 113 deletions

File tree

components/organization/OrganizationAnalytics.tsx

Lines changed: 368 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,375 @@
1-
import React from 'react';
1+
'use client';
2+
3+
import React, { useMemo } from 'react';
4+
import Link from 'next/link';
5+
import {
6+
Users,
7+
Trophy,
8+
HandCoins,
9+
UserPlus,
10+
CheckCircle2,
11+
ArrowUpRight,
12+
TrendingUp,
13+
TrendingDown,
14+
} from 'lucide-react';
15+
import {
16+
ChartConfig,
17+
ChartContainer,
18+
ChartTooltip,
19+
ChartTooltipContent,
20+
} from '@/components/ui/chart';
21+
import { Line, LineChart, XAxis } from 'recharts';
22+
import {
23+
useOrganizationStats,
24+
useOrganization,
25+
useOrganizationProfileCompletion,
26+
} from '@/lib/providers';
27+
import { useOrganizationAnalytics } from '@/hooks/use-organization-analytics';
28+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
29+
import { Progress } from '@/components/ui/progress';
30+
import LoadingSpinner from '@/components/LoadingSpinner';
231

332
const OrganizationAnalytics = () => {
33+
const { activeOrg, isLoading } = useOrganization();
34+
const stats = useOrganizationStats();
35+
const { isComplete, percentage, missingFields } =
36+
useOrganizationProfileCompletion();
37+
const { analytics, isLoading: isLoadingAnalytics } = useOrganizationAnalytics(
38+
{
39+
organizationId: activeOrg?._id,
40+
enabled: !!activeOrg?._id,
41+
}
42+
);
43+
44+
// Use real chart data from API
45+
const chartData = useMemo(() => {
46+
if (!analytics?.timeSeries?.hackathons) {
47+
return [];
48+
}
49+
50+
return analytics.timeSeries.hackathons.map(item => ({
51+
month: item.month,
52+
hackathons: item.count,
53+
}));
54+
}, [analytics]);
55+
56+
const chartConfig = {
57+
hackathons: {
58+
label: 'Hackathons',
59+
color: '#a7f950',
60+
},
61+
} satisfies ChartConfig;
62+
63+
// Use real trends from API
64+
const trends = useMemo(() => {
65+
if (!analytics?.trends) {
66+
return {
67+
members: { change: 0, isPositive: false },
68+
hackathons: { change: 0, isPositive: false },
69+
grants: { change: 0, isPositive: false },
70+
};
71+
}
72+
73+
return {
74+
members: {
75+
change: analytics.trends.members.change,
76+
isPositive: analytics.trends.members.isPositive,
77+
},
78+
hackathons: {
79+
change: analytics.trends.hackathons.change,
80+
isPositive: analytics.trends.hackathons.isPositive,
81+
},
82+
grants: {
83+
change: analytics.trends.grants.change,
84+
isPositive: analytics.trends.grants.isPositive,
85+
},
86+
};
87+
}, [analytics]);
88+
89+
if (isLoading || !activeOrg || isLoadingAnalytics) {
90+
return (
91+
<div className='flex h-[70vh] items-center justify-center'>
92+
<div className='flex flex-col items-center gap-3 text-zinc-400'>
93+
<LoadingSpinner size='lg' color='primary' variant='spinner' />
94+
<p className='text-sm text-zinc-400'>Loading analytics...</p>
95+
</div>
96+
</div>
97+
);
98+
}
99+
100+
const statCards = [
101+
{
102+
title: 'Total Members',
103+
value: stats.memberCount,
104+
icon: Users,
105+
description: 'Active organization members',
106+
color: 'text-blue-400',
107+
bgColor: 'bg-blue-500/10',
108+
href: `/organizations/${activeOrg._id}/settings?tab=members`,
109+
trend: trends.members,
110+
},
111+
{
112+
title: 'Hackathons',
113+
value: stats.hackathonCount,
114+
icon: Trophy,
115+
description: 'Total hackathons hosted',
116+
color: 'text-yellow-400',
117+
bgColor: 'bg-yellow-500/10',
118+
href: `/organizations/${activeOrg._id}/hackathons`,
119+
trend: trends.hackathons,
120+
},
121+
{
122+
title: 'Grants',
123+
value: stats.grantCount,
124+
icon: HandCoins,
125+
description: 'Total grants available',
126+
color: 'text-green-400',
127+
bgColor: 'bg-green-500/10',
128+
href: `/organizations/${activeOrg._id}/grants`,
129+
trend: trends.grants,
130+
},
131+
{
132+
title: 'Pending Invites',
133+
value: stats.pendingInviteCount,
134+
icon: UserPlus,
135+
description: 'Invitations awaiting response',
136+
color: 'text-orange-400',
137+
bgColor: 'bg-orange-500/10',
138+
href: `/organizations/${activeOrg._id}/settings?tab=members`,
139+
},
140+
];
141+
4142
return (
5-
<div className='bg-background-main-bg text-white'>
6-
<div className='flex flex-col gap-4'>
7-
<h1 className='text-2xl font-bold'>Analytics</h1>
8-
<div className='flex flex-col gap-4'>
9-
<div className='flex flex-col gap-2'>
10-
<h2 className='text-lg font-bold'>Total Participants</h2>
11-
<p className='text-sm text-gray-400'>1,234</p>
12-
</div>
143+
<div className='bg-background-main-bg min-h-screen p-6 text-white'>
144+
<div className='mx-auto max-w-7xl'>
145+
{/* Header */}
146+
<div className='mb-8'>
147+
<h1 className='text-2xl font-bold text-white'>Analytics</h1>
148+
<p className='mt-2 text-sm text-gray-400'>
149+
Overview of your organization's key metrics
150+
</p>
151+
</div>
152+
153+
{/* Stats Cards Grid */}
154+
<div className='mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4'>
155+
{statCards.map((stat, index) => {
156+
const Icon = stat.icon;
157+
const TrendIcon = stat.trend?.isPositive
158+
? TrendingUp
159+
: TrendingDown;
160+
const trendColor = stat.trend?.isPositive
161+
? 'text-green-400'
162+
: 'text-red-400';
163+
164+
return (
165+
<Link key={index} href={stat.href || '#'}>
166+
<Card className='bg-background-card border-gray-900 transition-all hover:border-gray-800 hover:shadow-lg'>
167+
<CardHeader className='flex flex-row items-center justify-between pb-2'>
168+
<div
169+
className={`flex h-10 w-10 items-center justify-center rounded-lg ${stat.bgColor} ${stat.color}`}
170+
>
171+
<Icon className='h-5 w-5' />
172+
</div>
173+
{stat.trend && (
174+
<div className={`flex items-center gap-1 ${trendColor}`}>
175+
<TrendIcon className='h-4 w-4' />
176+
<span className='text-xs font-medium'>
177+
{stat.trend.change > 0 ? '+' : ''}
178+
{stat.trend.change}
179+
</span>
180+
</div>
181+
)}
182+
</CardHeader>
183+
<CardContent className='pt-0'>
184+
<div className='flex items-center justify-between'>
185+
<p className='text-2xl font-bold text-white'>
186+
{stat.value}
187+
</p>
188+
<ArrowUpRight className='h-4 w-4 text-gray-500' />
189+
</div>
190+
<p className='mt-1 text-sm font-medium text-gray-300'>
191+
{stat.title}
192+
</p>
193+
<p className='mt-1 text-xs text-gray-500'>
194+
{stat.description}
195+
</p>
196+
</CardContent>
197+
</Card>
198+
</Link>
199+
);
200+
})}
201+
</div>
202+
203+
{/* Profile Completion Card */}
204+
<div className='mb-8'>
205+
<Card className='bg-background-card border-gray-900'>
206+
<CardHeader>
207+
<CardTitle className='flex items-center justify-between'>
208+
<span className='text-lg font-semibold text-white'>
209+
Profile Completion
210+
</span>
211+
{isComplete ? (
212+
<CheckCircle2 className='h-5 w-5 text-green-400' />
213+
) : (
214+
<span className='text-sm font-normal text-gray-400'>
215+
{percentage}%
216+
</span>
217+
)}
218+
</CardTitle>
219+
</CardHeader>
220+
<CardContent>
221+
<div className='space-y-4'>
222+
<Progress value={percentage} className='h-3' />
223+
{!isComplete && missingFields.length > 0 && (
224+
<div className='mt-4'>
225+
<p className='mb-2 text-sm text-gray-400'>
226+
Missing fields:
227+
</p>
228+
<div className='flex flex-wrap gap-2'>
229+
{missingFields.map((field, idx) => (
230+
<span
231+
key={idx}
232+
className='rounded-md bg-gray-900 px-2 py-1 text-xs text-gray-300'
233+
>
234+
{field}
235+
</span>
236+
))}
237+
</div>
238+
<Link
239+
href={`/organizations/${activeOrg._id}/settings?tab=profile`}
240+
className='text-primary mt-3 inline-flex items-center gap-2 text-sm hover:underline'
241+
>
242+
Complete Profile
243+
<ArrowUpRight className='h-4 w-4' />
244+
</Link>
245+
</div>
246+
)}
247+
</div>
248+
</CardContent>
249+
</Card>
13250
</div>
14-
<div className='flex flex-col gap-4'>
15-
<div className='flex flex-col gap-2'>
16-
<h2 className='text-lg font-bold'>Total Participants</h2>
17-
<p className='text-sm text-gray-400'>1,234</p>
18-
</div>
251+
252+
{/* Hackathons Over Time Chart */}
253+
<div className='mb-8'>
254+
<Card className='bg-background-card border-gray-900'>
255+
<CardHeader>
256+
<div className='flex items-center justify-between'>
257+
<CardTitle className='text-lg font-semibold text-white'>
258+
Hackathons Over Time
259+
</CardTitle>
260+
<Link
261+
href={`/organizations/${activeOrg._id}/hackathons`}
262+
className='text-primary flex items-center gap-2 text-sm hover:underline'
263+
>
264+
View All
265+
<ArrowUpRight className='h-4 w-4' />
266+
</Link>
267+
</div>
268+
</CardHeader>
269+
<CardContent>
270+
<div className='mt-4'>
271+
{chartData.length > 0 ? (
272+
<ChartContainer
273+
config={chartConfig}
274+
className='h-[300px] w-full'
275+
>
276+
<LineChart
277+
accessibilityLayer
278+
data={chartData}
279+
margin={{
280+
left: 8,
281+
right: 8,
282+
top: 8,
283+
bottom: 8,
284+
}}
285+
>
286+
<XAxis
287+
dataKey='month'
288+
tickLine={false}
289+
axisLine={false}
290+
tickMargin={8}
291+
tick={{ fontSize: 12, fill: '#9CA3AF' }}
292+
/>
293+
<ChartTooltip
294+
cursor={false}
295+
content={
296+
<ChartTooltipContent
297+
className='rounded-lg border border-gray-800 bg-gray-900 text-white'
298+
labelClassName='text-gray-300'
299+
/>
300+
}
301+
/>
302+
<Line
303+
dataKey='hackathons'
304+
type='monotone'
305+
stroke='var(--color-hackathons)'
306+
strokeWidth={2}
307+
dot={false}
308+
/>
309+
</LineChart>
310+
</ChartContainer>
311+
) : (
312+
<div className='flex h-[300px] items-center justify-center text-gray-400'>
313+
<p className='text-sm'>No hackathon data available</p>
314+
</div>
315+
)}
316+
</div>
317+
</CardContent>
318+
</Card>
319+
</div>
320+
321+
{/* Quick Actions */}
322+
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3'>
323+
<Link href={`/organizations/${activeOrg._id}/hackathons/new`}>
324+
<Card className='bg-background-card hover:border-primary/50 border-gray-900 transition-all hover:shadow-lg'>
325+
<CardContent className='flex items-center gap-4 p-6'>
326+
<div className='bg-primary/10 text-primary flex h-12 w-12 items-center justify-center rounded-lg'>
327+
<Trophy className='h-6 w-6' />
328+
</div>
329+
<div className='flex-1'>
330+
<h3 className='font-semibold text-white'>Create Hackathon</h3>
331+
<p className='text-sm text-gray-400'>
332+
Start a new hackathon event
333+
</p>
334+
</div>
335+
<ArrowUpRight className='h-5 w-5 text-gray-500' />
336+
</CardContent>
337+
</Card>
338+
</Link>
339+
340+
<Link href={`/organizations/${activeOrg._id}/settings?tab=members`}>
341+
<Card className='bg-background-card hover:border-primary/50 border-gray-900 transition-all hover:shadow-lg'>
342+
<CardContent className='flex items-center gap-4 p-6'>
343+
<div className='flex h-12 w-12 items-center justify-center rounded-lg bg-blue-500/10 text-blue-400'>
344+
<Users className='h-6 w-6' />
345+
</div>
346+
<div className='flex-1'>
347+
<h3 className='font-semibold text-white'>Manage Members</h3>
348+
<p className='text-sm text-gray-400'>
349+
View and manage team members
350+
</p>
351+
</div>
352+
<ArrowUpRight className='h-5 w-5 text-gray-500' />
353+
</CardContent>
354+
</Card>
355+
</Link>
356+
357+
<Link href={`/organizations/${activeOrg._id}/settings`}>
358+
<Card className='bg-background-card hover:border-primary/50 border-gray-900 transition-all hover:shadow-lg'>
359+
<CardContent className='flex items-center gap-4 p-6'>
360+
<div className='flex h-12 w-12 items-center justify-center rounded-lg bg-gray-500/10 text-gray-400'>
361+
<CheckCircle2 className='h-6 w-6' />
362+
</div>
363+
<div className='flex-1'>
364+
<h3 className='font-semibold text-white'>Settings</h3>
365+
<p className='text-sm text-gray-400'>
366+
Configure organization settings
367+
</p>
368+
</div>
369+
<ArrowUpRight className='h-5 w-5 text-gray-500' />
370+
</CardContent>
371+
</Card>
372+
</Link>
19373
</div>
20374
</div>
21375
</div>

0 commit comments

Comments
 (0)