From fa0df9ed40c9085885bd65dfed69f73b0fa9012a Mon Sep 17 00:00:00 2001 From: ssbg04 Date: Tue, 8 Sep 2026 20:36:17 +0800 Subject: [PATCH] feat(dashboard): show document status distribution count over total requirements --- backend/src/controllers/dashboardController.js | 12 ++++++++---- frontend/AGENTS.md | 7 +++++-- frontend/lib/domain/entities/dashboard_models.dart | 8 +++++++- .../ui/screens/dashboard/widgets/dashboard_kpis.dart | 12 +++++++++--- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/backend/src/controllers/dashboardController.js b/backend/src/controllers/dashboardController.js index 4d6c333..4f280fe 100644 --- a/backend/src/controllers/dashboardController.js +++ b/backend/src/controllers/dashboardController.js @@ -495,9 +495,11 @@ exports.getKpis = (req, res) => { ) `).get(userId)?.total || 0; + const totalRequirementsTeacher = studentDocStats.reduce((sum, s) => sum + s.totalRequired, 0); + statusDistribution = [ - { status: 'Completed', count: totalCompletedReqsTeacher }, - { status: 'Archived', count: archivedDocsTeacher } + { status: 'Completed', count: totalCompletedReqsTeacher, total: totalRequirementsTeacher }, + { status: 'Archived', count: archivedDocsTeacher, total: totalRequirementsTeacher } ]; docTypeBreakdown = Object.entries(docTypeByGrade) @@ -789,9 +791,11 @@ exports.getKpis = (req, res) => { AND ay.year_range = ? `).get(activeAy.year_range)?.total || 0 : 0; + const totalRequirementsAdmin = studentDocStats.reduce((sum, s) => sum + s.totalRequired, 0); + statusDistribution = [ - { status: 'Completed', count: totalCompletedReqsAdmin }, - { status: 'Archived', count: archivedDocsAdmin } + { status: 'Completed', count: totalCompletedReqsAdmin, total: totalRequirementsAdmin }, + { status: 'Archived', count: archivedDocsAdmin, total: totalRequirementsAdmin } ]; docTypeBreakdown = Object.entries(docTypeByGrade) diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index 56c3891..fd1e0e6 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -456,11 +456,14 @@ Files backup/restore - mirror mode or zip --> -# REPORT SCREEN + + + + --- diff --git a/frontend/lib/domain/entities/dashboard_models.dart b/frontend/lib/domain/entities/dashboard_models.dart index a640bd0..16753e6 100644 --- a/frontend/lib/domain/entities/dashboard_models.dart +++ b/frontend/lib/domain/entities/dashboard_models.dart @@ -216,11 +216,17 @@ class ActivityByDayEntry { class StatusDistributionEntry { final String status; final int count; - const StatusDistributionEntry({required this.status, required this.count}); + final int total; + const StatusDistributionEntry({ + required this.status, + required this.count, + this.total = 0, + }); factory StatusDistributionEntry.fromJson(Map j) => StatusDistributionEntry( status: j['status'] as String? ?? '', count: (j['count'] as num?)?.toInt() ?? 0, + total: (j['total'] as num?)?.toInt() ?? 0, ); } diff --git a/frontend/lib/ui/screens/dashboard/widgets/dashboard_kpis.dart b/frontend/lib/ui/screens/dashboard/widgets/dashboard_kpis.dart index 120bb99..e766bb9 100644 --- a/frontend/lib/ui/screens/dashboard/widgets/dashboard_kpis.dart +++ b/frontend/lib/ui/screens/dashboard/widgets/dashboard_kpis.dart @@ -620,8 +620,12 @@ class _StatusDistributionCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...entries.map((e) { - final pct = total == 0 ? 0.0 : e.count / total; + final targetTotal = e.total > 0 ? e.total : total; + final pct = targetTotal == 0 ? 0.0 : (e.count / targetTotal).clamp(0.0, 1.0); final color = _colorMap[e.status] ?? (isDark ? Colors.grey.shade400 : Colors.grey.shade500); + final countLabel = e.total > 0 + ? '${e.count}/${e.total} (${(pct * 100).toStringAsFixed(1)}%)' + : '${e.count} (${(pct * 100).toStringAsFixed(1)}%)'; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( @@ -639,7 +643,7 @@ class _StatusDistributionCard extends StatelessWidget { ), ), Text( - '${e.count} (${(pct * 100).toStringAsFixed(1)}%)', + countLabel, style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), @@ -683,7 +687,9 @@ class _StatusDistributionCard extends StatelessWidget { ), ), Text( - '$total', + entries.any((e) => e.total > 0) + ? '$total/${entries.firstWhere((e) => e.total > 0).total}' + : '$total', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold,