diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 21c30d4..b199cf0 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -75,7 +75,7 @@ services: - event-net ports: - "3002:3000" - command: sh -c "npx prisma generate && npm run dev" + command: sh -c "npx prisma generate && npx prisma migrate deploy && npm run prisma:seed && npm run dev" depends_on: auth-service-db: condition: service_healthy @@ -103,7 +103,7 @@ services: - /app/prisma ports: - "3003:3000" - command: sh -c "npx prisma generate && npm run dev" + command: sh -c "npx prisma generate && npx prisma migrate deploy && npm run prisma:seed && npm run dev" networks: - event-net depends_on: @@ -135,7 +135,7 @@ services: - /app/prisma ports: - "3004:3000" - command: sh -c "npx prisma generate && npm run dev" + command: sh -c "npx prisma generate && npx prisma migrate deploy && npm run dev" networks: - event-net depends_on: @@ -165,7 +165,7 @@ services: - /app/prisma ports: - "3005:3000" - command: sh -c "npx prisma generate && npm run dev" + command: sh -c "npx prisma generate && npx prisma migrate deploy && npm run dev" networks: - event-net depends_on: @@ -219,7 +219,7 @@ services: - /app/prisma ports: - "3007:3000" - command: sh -c "npx prisma generate && npm run dev" + command: sh -c "npx prisma generate && npx prisma migrate deploy && npm run dev" networks: - event-net depends_on: diff --git a/ems-client/app/dashboard/admin/page.tsx b/ems-client/app/dashboard/admin/page.tsx index 1998b66..be410fb 100644 --- a/ems-client/app/dashboard/admin/page.tsx +++ b/ems-client/app/dashboard/admin/page.tsx @@ -26,6 +26,8 @@ import { useEffect, useState, useCallback } from "react"; import {useLogger} from "@/lib/logger/LoggerProvider"; import {withAdminAuth} from "@/components/hoc/withAuth"; import { eventAPI } from "@/lib/api/event.api"; +import { authAPI } from "@/lib/api/auth.api"; +import { bookingAPI } from "@/lib/api/booking.api"; import { EventResponse, EventStatus } from "@/lib/api/types/event.types"; const LOGGER_COMPONENT_NAME = 'AdminDashboard'; @@ -105,13 +107,15 @@ function AdminDashboard() { logger.debug(LOGGER_COMPONENT_NAME, 'Fetching dashboard stats'); // Fetch all events to get stats + // Note: API limit is 100, so we fetch with max limit and use total from response const eventsResponse = await eventAPI.getAllEvents({ - limit: 1000, // Large limit to get all events + limit: 100, // Max allowed limit page: 1 }); if (eventsResponse.success && eventsResponse.data) { const allEvents = eventsResponse.data.events; + // Use total from API response, which represents total across all pages const totalEvents = eventsResponse.data.total || allEvents.length; const activeEvents = allEvents.filter(e => e.status === EventStatus.PUBLISHED).length; @@ -122,13 +126,34 @@ function AdminDashboard() { return startDate > now && e.status === EventStatus.PUBLISHED; }).length; - // Total registrations: Currently no admin endpoint to get all bookings across all events - // TODO: Implement admin endpoint: GET /api/admin/bookings/stats or similar - // For now, we'll leave it as null and show "N/A" + console.log("Getting Total Registrations"); + + // Total registrations: Get from booking service admin endpoint let totalRegistrations: number | null = null; + try { + const registrationsResponse = await bookingAPI.getTotalRegistrations(); + if (registrationsResponse.success && registrationsResponse.data) { + totalRegistrations = registrationsResponse.data.totalRegistrations; + } + } catch (error) { + logger.error(LOGGER_COMPONENT_NAME, 'Failed to fetch total registrations', error as Error); + // Leave as null to show "N/A" + } + + // Total users: Get from auth service admin endpoint + let totalUsers: number | null = null; + try { + const usersResponse = await authAPI.getTotalUsers(); + if (usersResponse.success && usersResponse.data) { + totalUsers = usersResponse.data.totalUsers; + } + } catch (error) { + logger.error(LOGGER_COMPONENT_NAME, 'Failed to fetch total users', error as Error); + // Leave as null to show "N/A" + } setStats({ - totalUsers: null, // TODO: No API endpoint available yet + totalUsers, totalEvents, activeEvents, flaggedUsers: mockFlaggedUsers.length, @@ -137,6 +162,7 @@ function AdminDashboard() { }); logger.info(LOGGER_COMPONENT_NAME, 'Dashboard stats fetched successfully', { + totalUsers, totalEvents, activeEvents, upcomingEvents, diff --git a/ems-client/app/dashboard/admin/reports/page.tsx b/ems-client/app/dashboard/admin/reports/page.tsx index f0ca372..36a03c0 100644 --- a/ems-client/app/dashboard/admin/reports/page.tsx +++ b/ems-client/app/dashboard/admin/reports/page.tsx @@ -4,41 +4,32 @@ import { useAuth } from "@/lib/auth-context"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { ArrowLeft, BarChart3, TrendingUp, Users, Calendar, Download, Filter } from "lucide-react"; +import { ArrowLeft, BarChart3, TrendingUp, Users, Calendar, Download, Filter, RefreshCw } from "lucide-react"; import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { useEffect, useState, useCallback } from "react"; import {logger} from "@/lib/logger"; +import { eventAPI } from "@/lib/api/event.api"; +import { bookingAPI } from "@/lib/api/booking.api"; +import { authAPI } from "@/lib/api/auth.api"; const COMPONENT_NAME = 'ReportsPage'; -// Mock data for reports -const mockReportData = { - totalEvents: 8, - totalUsers: 156, - totalRegistrations: 342, - averageAttendance: 78, - topEvents: [ - { name: 'Tech Conference 2024', registrations: 156, attendance: 89 }, - { name: 'AI Summit', registrations: 142, attendance: 95 }, - { name: 'Design Workshop', registrations: 44, attendance: 67 } - ], - userGrowth: [ - { month: 'Oct 2023', users: 45 }, - { month: 'Nov 2023', users: 67 }, - { month: 'Dec 2023', users: 89 }, - { month: 'Jan 2024', users: 112 }, - { month: 'Feb 2024', users: 156 } - ], - eventStats: [ - { status: 'Published', count: 5, percentage: 62.5 }, - { status: 'Draft', count: 2, percentage: 25 }, - { status: 'Archived', count: 1, percentage: 12.5 } - ] -}; +interface ReportData { + totalEvents: number; + totalUsers: number; + totalRegistrations: number; + averageAttendance: number; + topEvents: Array<{ eventId: string; name: string; registrations: number; attendance: number }>; + userGrowth: Array<{ month: string; users: number }>; + eventStats: Array<{ status: string; count: number; percentage: number }>; +} export default function ReportsPage() { const { user, isAuthenticated, isLoading } = useAuth(); const router = useRouter(); + const [reportData, setReportData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); useEffect(() => { if (!isLoading && !isAuthenticated) { @@ -48,12 +39,74 @@ export default function ReportsPage() { } }, [isAuthenticated, isLoading, user, router]); + const fetchReportData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + // Fetch all analytics data in parallel + const [totalEventsRes, totalUsersRes, totalRegistrationsRes, averageAttendanceRes, topEventsRes, userGrowthRes, eventStatsRes] = await Promise.all([ + eventAPI.getTotalEvents(), + authAPI.getTotalUsers(), + bookingAPI.getTotalRegistrations(), + bookingAPI.getAverageAttendance(), + bookingAPI.getTopEvents(10), + authAPI.getUserGrowth(), + eventAPI.getEventStatsByStatus() + ]); + + // Fetch event names for top events + const topEventsWithNames = await Promise.all( + topEventsRes.data.map(async (event: any) => { + try { + const eventRes = await eventAPI.getEventById(event.eventId); + return { + ...event, + name: eventRes.data.name || 'Unknown Event' + }; + } catch (err) { + logger.error(COMPONENT_NAME, 'Failed to fetch event name', err as Error); + return { + ...event, + name: 'Unknown Event' + }; + } + }) + ); + + setReportData({ + totalEvents: totalEventsRes.data.totalEvents, + totalUsers: totalUsersRes.data.totalUsers, + totalRegistrations: totalRegistrationsRes.data.totalRegistrations, + averageAttendance: averageAttendanceRes.data.averageAttendance, + topEvents: topEventsWithNames, + userGrowth: userGrowthRes.data, + eventStats: eventStatsRes.data.map((stat: any) => ({ + status: stat.status, + count: stat.count, + percentage: stat.percentage + })) + }); + } catch (err) { + logger.error(COMPONENT_NAME, 'Failed to fetch report data', err as Error); + setError('Failed to load analytics data. Please try again.'); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + if (isAuthenticated && user?.role === 'ADMIN') { + fetchReportData(); + } + }, [isAuthenticated, user, fetchReportData]); + const handleExportReport = (type: string) => { // TODO: Implement report export functionality logger.debug(COMPONENT_NAME, `Exporting ${type} report`); }; - if (isLoading) { + if (isLoading || loading) { return (
@@ -68,6 +121,48 @@ export default function ReportsPage() { return null; } + if (error) { + return ( +
+
+
+
+
+ +

+ Reports & Analytics +

+
+
+
+
+
+ + +

{error}

+ +
+
+
+
+ ); + } + + if (!reportData) { + return null; + } + return (
{/* Header */} @@ -75,8 +170,8 @@ export default function ReportsPage() {
-
{/* Key Metrics */} @@ -112,7 +213,7 @@ export default function ReportsPage() {

Total Events

-

{mockReportData.totalEvents}

+

{reportData.totalEvents}

@@ -124,7 +225,7 @@ export default function ReportsPage() {

Total Users

-

{mockReportData.totalUsers}

+

{reportData.totalUsers}

@@ -136,7 +237,7 @@ export default function ReportsPage() {

Total Registrations

-

{mockReportData.totalRegistrations}

+

{reportData.totalRegistrations}

@@ -148,7 +249,7 @@ export default function ReportsPage() {

Avg. Attendance

-

{mockReportData.averageAttendance}%

+

{reportData.averageAttendance.toFixed(1)}%

@@ -167,26 +268,26 @@ export default function ReportsPage() {
- - - - -