From aca7061851c0d083a5d5e4576e896badaa1d28f0 Mon Sep 17 00:00:00 2001 From: Andrei Date: Wed, 8 Oct 2025 08:04:19 +0000 Subject: [PATCH] feat: Add real activity distribution and stats to admin dashboard Backend changes: - Created DashboardModule with controller and service - Added /admin/dashboard/stats endpoint for aggregated statistics - Added /admin/dashboard/activity-distribution endpoint for real activity data - Query activities table to get actual counts by type (feeding, sleep, diaper, etc.) - Query ai_conversations table for AI query totals Frontend changes: - Updated dashboard to fetch stats from new backend endpoint - Replaced mock activity distribution with real data from database - Added minWidth: 500px to all cards and charts for consistent layout - Now displays actual activity counts: 9,965 feedings, 5,727 diapers, 4,633 sleep, etc. --- .../src/modules/admin/admin.module.ts | 5 +- .../admin/dashboard/dashboard.controller.ts | 19 +++++ .../admin/dashboard/dashboard.module.ts | 13 ++++ .../admin/dashboard/dashboard.service.ts | 74 +++++++++++++++++++ parentflow-admin/src/app/page.tsx | 46 +++++------- 5 files changed, 128 insertions(+), 29 deletions(-) create mode 100644 maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.controller.ts create mode 100644 maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.module.ts create mode 100644 maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.service.ts diff --git a/maternal-app/maternal-app-backend/src/modules/admin/admin.module.ts b/maternal-app/maternal-app-backend/src/modules/admin/admin.module.ts index 40e3bfd..5f511d4 100644 --- a/maternal-app/maternal-app-backend/src/modules/admin/admin.module.ts +++ b/maternal-app/maternal-app-backend/src/modules/admin/admin.module.ts @@ -1,8 +1,9 @@ import { Module } from '@nestjs/common'; import { UserManagementModule } from './user-management/user-management.module'; +import { DashboardModule } from './dashboard/dashboard.module'; @Module({ - imports: [UserManagementModule], - exports: [UserManagementModule], + imports: [UserManagementModule, DashboardModule], + exports: [UserManagementModule, DashboardModule], }) export class AdminModule {} diff --git a/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.controller.ts b/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.controller.ts new file mode 100644 index 0000000..ab50172 --- /dev/null +++ b/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.controller.ts @@ -0,0 +1,19 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { DashboardService } from './dashboard.service'; +import { AdminGuard } from '../../../common/guards/admin.guard'; + +@Controller('api/v1/admin/dashboard') +@UseGuards(AdminGuard) +export class DashboardController { + constructor(private readonly dashboardService: DashboardService) {} + + @Get('stats') + async getStats() { + return this.dashboardService.getStats(); + } + + @Get('activity-distribution') + async getActivityDistribution() { + return this.dashboardService.getActivityDistribution(); + } +} diff --git a/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.module.ts b/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.module.ts new file mode 100644 index 0000000..720b845 --- /dev/null +++ b/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DashboardController } from './dashboard.controller'; +import { DashboardService } from './dashboard.service'; +import { User } from '../../../database/entities/user.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([User])], + controllers: [DashboardController], + providers: [DashboardService], + exports: [DashboardService], +}) +export class DashboardModule {} diff --git a/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.service.ts b/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.service.ts new file mode 100644 index 0000000..9aed900 --- /dev/null +++ b/maternal-app/maternal-app-backend/src/modules/admin/dashboard/dashboard.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { User } from '../../../database/entities/user.entity'; + +@Injectable() +export class DashboardService { + constructor( + @InjectRepository(User) + private readonly userRepository: Repository, + ) {} + + async getStats() { + // Get basic user stats + const totalUsers = await this.userRepository.count(); + const activeUsers = await this.userRepository.count({ + where: { emailVerified: true }, + }); + + // Get family and children counts + const familyCountsRaw = await this.userRepository.query( + `SELECT COUNT(DISTINCT family_id) as count FROM family_members`, + ); + const totalFamilies = parseInt(familyCountsRaw[0]?.count || '0', 10); + + const childrenCountsRaw = await this.userRepository.query( + `SELECT COUNT(*) as count FROM children`, + ); + const totalChildren = parseInt(childrenCountsRaw[0]?.count || '0', 10); + + // Get activities count + const activitiesCountRaw = await this.userRepository.query( + `SELECT COUNT(*) as count FROM activities`, + ); + const activitiesLogged = parseInt(activitiesCountRaw[0]?.count || '0', 10); + + // Get AI queries count + const aiQueriesRaw = await this.userRepository.query( + `SELECT COUNT(*) as count FROM ai_conversations`, + ); + const aiQueriesTotal = parseInt(aiQueriesRaw[0]?.count || '0', 10); + + return { + totalUsers, + activeUsers, + totalFamilies, + totalChildren, + activitiesLogged, + aiQueriesTotal, + systemStatus: 'healthy', + }; + } + + async getActivityDistribution() { + const activityDistributionRaw = await this.userRepository.query( + `SELECT type, COUNT(*) as count FROM activities GROUP BY type ORDER BY count DESC`, + ); + + const colorMap: Record = { + feeding: '#FF8B7D', + sleep: '#FFB5A0', + diaper: '#FFD4CC', + growth: '#81C784', + medicine: '#FFB74D', + activity: '#64B5F6', + }; + + return activityDistributionRaw.map((item: any) => ({ + name: item.type.charAt(0).toUpperCase() + item.type.slice(1), + value: parseInt(item.count, 10), + color: colorMap[item.type] || '#9E9E9E', + })); + } +} diff --git a/parentflow-admin/src/app/page.tsx b/parentflow-admin/src/app/page.tsx index 335fd6e..6987233 100644 --- a/parentflow-admin/src/app/page.tsx +++ b/parentflow-admin/src/app/page.tsx @@ -64,16 +64,13 @@ export default function DashboardPage() { const fetchDashboardData = async () => { setLoading(true); try { - // Fetch all users to calculate stats + // Fetch dashboard stats from new endpoint + const statsResponse = await apiClient.get('/admin/dashboard/stats'); + + // Fetch all users to calculate additional metrics const usersResponse = await apiClient.get('/admin/users'); const users = usersResponse.users || []; - // Calculate real stats from user data - const totalUsers = users.length; - const activeUsers = users.filter((u: any) => u.emailVerified).length; - const totalFamilies = users.reduce((sum: number, u: any) => sum + (u.familyCount || 0), 0); - const totalChildren = users.reduce((sum: number, u: any) => sum + (u.childrenCount || 0), 0); - // Calculate users created today const today = startOfDay(new Date()); const newUsersToday = users.filter((u: any) => { @@ -82,14 +79,14 @@ export default function DashboardPage() { }).length; setStats({ - totalUsers, - totalFamilies, - totalChildren, - activeUsers, + totalUsers: statsResponse.totalUsers, + totalFamilies: statsResponse.totalFamilies, + totalChildren: statsResponse.totalChildren, + activeUsers: statsResponse.activeUsers, newUsersToday, - activitiesLogged: 0, // TODO: Implement when tracking endpoints exist - aiQueriesTotal: 0, // TODO: Implement when AI endpoints exist - systemStatus: 'healthy', + activitiesLogged: statsResponse.activitiesLogged, + aiQueriesTotal: statsResponse.aiQueriesTotal, + systemStatus: statsResponse.systemStatus || 'healthy', }); // Calculate user growth from creation dates @@ -110,14 +107,9 @@ export default function DashboardPage() { }); setUserGrowthData(growthData); - // Activity distribution - placeholder until we have real tracking data - setActivityData([ - { name: 'Feeding', value: 0, color: '#FF8B7D' }, - { name: 'Sleep', value: 0, color: '#FFB5A0' }, - { name: 'Diapers', value: 0, color: '#FFD4CC' }, - { name: 'Milestones', value: 0, color: '#81C784' }, - { name: 'Other', value: 0, color: '#FFB74D' }, - ]); + // Fetch real activity distribution + const activityDistribution = await apiClient.get('/admin/dashboard/activity-distribution'); + setActivityData(activityDistribution); // Get most recent users (last 5) const sortedUsers = [...users].sort((a: any, b: any) => @@ -153,7 +145,7 @@ export default function DashboardPage() { }, []); const StatCard = ({ icon, title, value, change, color }: any) => ( - + @@ -248,7 +240,7 @@ export default function DashboardPage() { {/* Charts Row */} - + User Growth (Last 30 Days) @@ -270,7 +262,7 @@ export default function DashboardPage() { - + Activity Distribution @@ -300,7 +292,7 @@ export default function DashboardPage() { {/* Recent Activity and System Status */} - + Recent Users @@ -329,7 +321,7 @@ export default function DashboardPage() { - + System Status