diff --git a/components/dashboard/AnalyticsCharts.tsx b/components/dashboard/AnalyticsCharts.tsx index cdd081f4..47e1bc1c 100644 --- a/components/dashboard/AnalyticsCharts.tsx +++ b/components/dashboard/AnalyticsCharts.tsx @@ -20,7 +20,7 @@ import { Button } from '@/components/ui/button' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { CompanyAnalytics } from '@/types/company' import { format } from 'date-fns' -import { TrendingUp, Eye, MousePointerClick, Users, Calendar, Download } from 'lucide-react' +import { TrendingUp, Eye, MousePointerClick, Users, Calendar, Download, Trophy } from 'lucide-react' interface AnalyticsChartsProps { analytics: CompanyAnalytics[] @@ -40,6 +40,8 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha registrations: record.total_registrations, eventsCreated: record.events_created, eventsPublished: record.events_published, + hackathonsCreated: record.hackathons_created, + hackathonsPublished: record.hackathons_published, })) // Calculate totals @@ -50,8 +52,10 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha registrations: acc.registrations + record.total_registrations, eventsCreated: acc.eventsCreated + record.events_created, eventsPublished: acc.eventsPublished + record.events_published, + hackathonsCreated: acc.hackathonsCreated + record.hackathons_created, + hackathonsPublished: acc.hackathonsPublished + record.hackathons_published, }), - { views: 0, clicks: 0, registrations: 0, eventsCreated: 0, eventsPublished: 0 } + { views: 0, clicks: 0, registrations: 0, eventsCreated: 0, eventsPublished: 0, hackathonsCreated: 0, hackathonsPublished: 0 } ) // Calculate averages @@ -70,7 +74,7 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha return (
{/* Summary Stats */} -
+
+
@@ -131,8 +142,11 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha
- {/* Event Performance Comparison */} - + {/* Event and Hackathon Performance Comparison */} +
+ + +
{/* Main Charts */} @@ -187,8 +201,9 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha - Engagement + Overall Engagement Events + Hackathons @@ -304,10 +319,10 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha @@ -320,7 +335,7 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha } /> - + ) : ( @@ -342,8 +357,8 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha @@ -351,6 +366,78 @@ export function AnalyticsCharts({ analytics, dateRange, onExport }: AnalyticsCha )} + + + {chartData.length === 0 ? ( +
+

No data available for the selected date range

+
+ ) : chartType === 'line' ? ( + + + + + + } /> + + + + + + ) : chartType === 'bar' ? ( + + + + + + } /> + + + + + + ) : ( + + + + + + } /> + + + + + + )} +
@@ -440,11 +527,27 @@ function EventPerformanceComparison({ analytics }: EventPerformanceComparisonPro const avgRegistrationsPerEvent = totalEvents > 0 ? Math.round(totalRegistrations / totalEvents) : 0 if (totalEvents === 0) { - return null + return ( + + + + + Event Performance Metrics + + Average performance per published event + + +
+ +

No events published in this period

+
+
+
+ ) } return ( - + @@ -454,38 +557,38 @@ function EventPerformanceComparison({ analytics }: EventPerformanceComparisonPro
-
+

Avg Views per Event

- +

{avgViewsPerEvent.toLocaleString()}

- {totalViews.toLocaleString()} total views across {totalEvents} events + {totalViews.toLocaleString()} total views across {totalEvents} {totalEvents === 1 ? 'event' : 'events'}

-
+

Avg Clicks per Event

{avgClicksPerEvent.toLocaleString()}

- {totalClicks.toLocaleString()} total clicks across {totalEvents} events + {totalClicks.toLocaleString()} total clicks across {totalEvents} {totalEvents === 1 ? 'event' : 'events'}

-
+

Avg Registrations per Event

- +

{avgRegistrationsPerEvent.toLocaleString()}

- {totalRegistrations.toLocaleString()} total registrations across {totalEvents} events + {totalRegistrations.toLocaleString()} total registrations across {totalEvents} {totalEvents === 1 ? 'event' : 'events'}

@@ -511,3 +614,109 @@ function EventPerformanceComparison({ analytics }: EventPerformanceComparisonPro ) } + +// Hackathon Performance Comparison Component +interface HackathonPerformanceComparisonProps { + analytics: CompanyAnalytics[] +} + +function HackathonPerformanceComparison({ analytics }: HackathonPerformanceComparisonProps) { + // Calculate performance metrics + const totalHackathons = analytics.reduce((sum, record) => sum + record.hackathons_published, 0) + const totalViews = analytics.reduce((sum, record) => sum + record.total_views, 0) + const totalClicks = analytics.reduce((sum, record) => sum + record.total_clicks, 0) + const totalRegistrations = analytics.reduce((sum, record) => sum + record.total_registrations, 0) + + // Calculate averages per hackathon + const avgViewsPerHackathon = totalHackathons > 0 ? Math.round(totalViews / totalHackathons) : 0 + const avgClicksPerHackathon = totalHackathons > 0 ? Math.round(totalClicks / totalHackathons) : 0 + const avgRegistrationsPerHackathon = totalHackathons > 0 ? Math.round(totalRegistrations / totalHackathons) : 0 + + if (totalHackathons === 0) { + return ( + + + + + Hackathon Performance Metrics + + Average performance per published hackathon + + +
+ +

No hackathons published in this period

+
+
+
+ ) + } + + return ( + + + + + Hackathon Performance Metrics + + Average performance per published hackathon + + +
+
+
+

Avg Views per Hackathon

+ +
+

{avgViewsPerHackathon.toLocaleString()}

+

+ {totalViews.toLocaleString()} total views across {totalHackathons} {totalHackathons === 1 ? 'hackathon' : 'hackathons'} +

+
+ +
+
+

Avg Clicks per Hackathon

+ +
+

{avgClicksPerHackathon.toLocaleString()}

+

+ {totalClicks.toLocaleString()} total clicks across {totalHackathons} {totalHackathons === 1 ? 'hackathon' : 'hackathons'} +

+
+ +
+
+

Avg Registrations per Hackathon

+ +
+

+ {avgRegistrationsPerHackathon.toLocaleString()} +

+

+ {totalRegistrations.toLocaleString()} total registrations across {totalHackathons} {totalHackathons === 1 ? 'hackathon' : 'hackathons'} +

+
+
+ +
+
+ +
+

Performance Insights

+

+ {totalHackathons === 1 + ? 'You have published 1 hackathon in this period.' + : `You have published ${totalHackathons} hackathons in this period.`}{' '} + {avgViewsPerHackathon > 150 && 'Your hackathons are attracting strong interest! '} + {avgRegistrationsPerHackathon > 15 && 'Excellent team registration rates! '} + {avgClicksPerHackathon < avgViewsPerHackathon * 0.1 && + 'Consider enhancing your hackathon descriptions to boost engagement.'} +

+
+
+
+
+
+ ) +} diff --git a/components/dashboard/CompanyDashboard.tsx b/components/dashboard/CompanyDashboard.tsx index f46407d6..d1906eb1 100644 --- a/components/dashboard/CompanyDashboard.tsx +++ b/components/dashboard/CompanyDashboard.tsx @@ -32,6 +32,16 @@ interface CompanyDashboardStats { totalViews: number totalClicks: number pendingApprovals: number + eventMetrics: { + views: number + registrations: number + clicks: number + } + hackathonMetrics: { + views: number + registrations: number + clicks: number + } recentChange?: { events: number registrations: number @@ -41,7 +51,7 @@ interface CompanyDashboardStats { interface RecentActivity { id: string - type: 'event_created' | 'event_approved' | 'event_rejected' | 'registration' | 'member_joined' + type: 'event_created' | 'event_approved' | 'event_rejected' | 'hackathon_created' | 'hackathon_approved' | 'hackathon_rejected' | 'registration' | 'member_joined' title: string description: string timestamp: string @@ -60,6 +70,17 @@ interface UpcomingEvent { approval_status: string } +interface UpcomingHackathon { + id: string + title: string + slug: string + date: string + mode: string + registrations: number + max_team_size: number + approval_status: string +} + interface CompanyDashboardProps { company: Company } @@ -69,6 +90,7 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { const [stats, setStats] = useState(null) const [recentActivity, setRecentActivity] = useState([]) const [upcomingEvents, setUpcomingEvents] = useState([]) + const [upcomingHackathons, setUpcomingHackathons] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -94,6 +116,8 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { const analyticsData = await analyticsResponse.json() + console.log('Analytics data:', analyticsData) + // Fetch events for upcoming events and pending approvals const eventsResponse = await fetch( `/api/companies/${company.slug}/events?limit=100` @@ -116,6 +140,9 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { const hackathonsData = await hackathonsResponse.json() + console.log('Hackathons data:', hackathonsData) + console.log('Hackathons array:', hackathonsData.hackathons) + // Calculate stats /* eslint-disable @typescript-eslint/no-explicit-any */ const pendingEvents = eventsData.events?.filter( @@ -138,6 +165,16 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { .sort((a: any, b: any) => new Date(a.date).getTime() - new Date(b.date).getTime()) .slice(0, 5) || [] + const upcomingHackathonsData = hackathonsData.hackathons + ?.filter((h: any) => { + const hackathonDate = new Date(h.date) + return hackathonDate > new Date() && h.approval_status === 'approved' + }) + .sort((a: any, b: any) => new Date(a.date).getTime() - new Date(b.date).getTime()) + .slice(0, 5) || [] + + console.log('Upcoming hackathons data:', upcomingHackathonsData) + // Calculate total registrations from both events and hackathons const eventRegistrations = eventsData.events?.reduce( (sum: number, e: any) => sum + (e.registered || 0), @@ -152,13 +189,34 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { const totalRegistrations = eventRegistrations + hackathonRegistrations /* eslint-enable @typescript-eslint/no-explicit-any */ + // Calculate separate metrics for events and hackathons from analytics + // Note: The analytics API doesn't currently separate views/clicks by type + // For now, we'll use the registration counts from the actual data + // and split views/clicks proportionally based on the number of each type + const totalItems = approvedEvents.length + approvedHackathons.length + const eventRatio = totalItems > 0 ? approvedEvents.length / totalItems : 0.5 + const hackathonRatio = totalItems > 0 ? approvedHackathons.length / totalItems : 0.5 + + const totalViews = analyticsData.summary?.total_views || 0 + const totalClicks = analyticsData.summary?.total_clicks || 0 + setStats({ totalEvents: approvedEvents.length, totalHackathons: approvedHackathons.length, totalRegistrations: totalRegistrations, - totalViews: analyticsData.summary?.total_views || 0, - totalClicks: analyticsData.summary?.total_clicks || 0, + totalViews: totalViews, + totalClicks: totalClicks, pendingApprovals: pendingEvents.length, + eventMetrics: { + views: Math.round(totalViews * eventRatio), + registrations: eventRegistrations, + clicks: Math.round(totalClicks * eventRatio), + }, + hackathonMetrics: { + views: Math.round(totalViews * hackathonRatio), + registrations: hackathonRegistrations, + clicks: Math.round(totalClicks * hackathonRatio), + }, recentChange: { events: 0, // Could calculate from analytics registrations: 0, @@ -167,8 +225,9 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { }) setUpcomingEvents(upcomingEventsData) + setUpcomingHackathons(upcomingHackathonsData) - // Generate recent activity from events + // Generate recent activity from events and hackathons const activities: RecentActivity[] = [] // Add recent events @@ -207,6 +266,42 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { }) } }) + + // Add recent hackathons + const recentHackathons = hackathonsData.hackathons?.slice(0, 3) || [] + recentHackathons.forEach((hackathon: any) => { + if (hackathon.approval_status === 'approved') { + activities.push({ + id: hackathon.id as string, + type: 'hackathon_approved', + title: 'Hackathon Approved', + description: `${hackathon.title} was approved and is now live`, + timestamp: (hackathon.approved_at || hackathon.created_at) as string, + icon: Trophy, + iconColor: 'text-orange-400', + }) + } else if (hackathon.approval_status === 'pending') { + activities.push({ + id: hackathon.id as string, + type: 'hackathon_created', + title: 'Hackathon Created', + description: `${hackathon.title} is pending approval`, + timestamp: hackathon.created_at as string, + icon: Clock, + iconColor: 'text-yellow-400', + }) + } else if (hackathon.approval_status === 'rejected') { + activities.push({ + id: hackathon.id as string, + type: 'hackathon_rejected', + title: 'Hackathon Rejected', + description: `${hackathon.title} was rejected`, + timestamp: hackathon.updated_at as string, + icon: AlertCircle, + iconColor: 'text-red-400', + }) + } + }) /* eslint-enable @typescript-eslint/no-explicit-any */ // Sort by timestamp @@ -257,7 +352,7 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { return (
- {/* Stats Cards */} + {/* Overview Stats Cards */}
+ {/* Separate Event and Hackathon Metrics */} +
+ {/* Event Metrics Card */} + + + + + Event Metrics + + Performance data for your events + + +
+
+
+ + Views +
+ + {stats.eventMetrics.views.toLocaleString()} + +
+
+
+ + Registrations +
+ + {stats.eventMetrics.registrations.toLocaleString()} + +
+
+
+ + Clicks +
+ + {stats.eventMetrics.clicks.toLocaleString()} + +
+ {stats.eventMetrics.views > 0 && ( +
+
+ Conversion Rate + + {((stats.eventMetrics.registrations / stats.eventMetrics.views) * 100).toFixed(1)}% + +
+
+ )} +
+
+
+ + {/* Hackathon Metrics Card */} + + + + + Hackathon Metrics + + Performance data for your hackathons + + +
+
+
+ + Views +
+ + {stats.hackathonMetrics.views.toLocaleString()} + +
+
+
+ + Registrations +
+ + {stats.hackathonMetrics.registrations.toLocaleString()} + +
+
+
+ + Clicks +
+ + {stats.hackathonMetrics.clicks.toLocaleString()} + +
+ {stats.hackathonMetrics.views > 0 && ( +
+
+ Conversion Rate + + {((stats.hackathonMetrics.registrations / stats.hackathonMetrics.views) * 100).toFixed(1)}% + +
+
+ )} +
+
+
+
+ {/* Main Content Grid */}
{/* Recent Activity */} @@ -311,7 +513,7 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) { Recent Activity - Latest updates and events + Latest updates from events and hackathons
@@ -320,7 +522,7 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) {

No recent activity

-

Create your first event to get started

+

Create your first event or hackathon to get started

) : (
@@ -376,6 +578,50 @@ export function CompanyDashboard({ company }: CompanyDashboardProps) {
+ {/* Upcoming Hackathons */} + + +
+
+ + + Upcoming Hackathons + + Your next scheduled hackathons +
+ +
+
+ + {upcomingHackathons.length === 0 ? ( +
+ +

No upcoming hackathons

+ +
+ ) : ( +
+ {upcomingHackathons.map((hackathon) => ( + + ))} +
+ )} +
+
+ {/* Quick Actions */} @@ -537,6 +783,43 @@ function UpcomingEventItem({ event }: UpcomingEventItemProps) { ) } +// Upcoming Hackathon Item Component +interface UpcomingHackathonItemProps { + hackathon: UpcomingHackathon + companySlug: string +} + +function UpcomingHackathonItem({ hackathon, companySlug }: UpcomingHackathonItemProps) { + return ( + +
+
+ +
+
+

+ {hackathon.title} +

+

+ {format(new Date(hackathon.date), 'MMM dd, yyyy')} +

+
+ + {hackathon.mode || 'Online'} + + + {hackathon.registrations || 0} teams registered + +
+
+
+ + ) +} + // Quick Action Button Component interface QuickActionButtonProps { href: string