feat(dashboard): isolate needs attention into dedicated endpoint and provider
This commit is contained in:
1 parent
2416a598a1
commit
502b2ea9f1
6 files changed
+38
-10
No files matched your search
+2
-1
@@ -1109,7 +1109,7 @@ Files backup/restore - mirror mode or zip -->
|
||||
- [x] Removed "Top Students by Documents" and "Needs Attention" cards from the bottom of the dashboard.
|
||||
- [x] Fixed "4 missing" empty list bug: student count and student modal list are derived synchronously and directly from matching students.
|
||||
- [x] Implemented dynamic missing document requirement chips (Approach A): dynamically evaluates and sorts missing counts across all enabled mandatory requirements (SF9, SF10, PSA, etc.) descending by missing frequency.
|
||||
- [x] Needs Attention section fix: synchronized backend KPI endpoint (`/api/dashboard/kpis`) to return exact mandatory missing document requirements, counts, and urgent student records for both Admin and Teacher roles; updated frontend models and `NeedsAttentionCard` to consume this synced dataset with direct profile and upload actions.
|
||||
- [x] Needs Attention section fix: created dedicated endpoint (`/api/dashboard/needs-attention`) filtering strictly active enrolled students (`s.status = 'Enrolled'`) and dedicated `dashboardNeedsAttentionProvider` to isolate alerts from other KPI chart metrics.
|
||||
|
||||
# STUDENT SCREEN
|
||||
- enrolled tab:
|
||||
@@ -1128,6 +1128,7 @@ Files backup/restore - mirror mode or zip -->
|
||||
- [x] Removed Recycle Bin, delete, restore, upload new version, and copy buttons from the Archive Screen (including folder context menus, document context menus, document preview modal, and bulk operations bar).
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -66,4 +66,15 @@ class DashboardRepository {
|
||||
throw Exception(e.response?.data['message'] ?? 'Failed to fetch KPIs.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<NeedsAttentionData> getNeedsAttention() async {
|
||||
try {
|
||||
final options = await _getAuthOptions();
|
||||
final response = await _dio.get('/dashboard/needs-attention', options: options);
|
||||
return NeedsAttentionData.fromJson(response.data as Map<String, dynamic>);
|
||||
} on DioException catch (e) {
|
||||
throw Exception(e.response?.data['message'] ?? 'Failed to fetch Needs Attention data.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class AuthNotifier extends AsyncNotifier<UserModel?> {
|
||||
);
|
||||
ref.invalidate(dashboardDataProvider);
|
||||
ref.invalidate(dashboardKpisProvider);
|
||||
ref.invalidate(dashboardNeedsAttentionProvider);
|
||||
state = AsyncData(user);
|
||||
ref.read(heartbeatServiceProvider).start(user);
|
||||
ForegroundSyncService.start();
|
||||
@@ -105,8 +106,10 @@ class AuthNotifier extends AsyncNotifier<UserModel?> {
|
||||
|
||||
ref.invalidate(dashboardDataProvider);
|
||||
ref.invalidate(dashboardKpisProvider);
|
||||
ref.invalidate(dashboardNeedsAttentionProvider);
|
||||
}
|
||||
|
||||
|
||||
Future<void> refreshUser() async {
|
||||
try {
|
||||
final repository = ref.read(authRepositoryProvider);
|
||||
|
||||
@@ -59,6 +59,18 @@ final dashboardKpisProvider = FutureProvider.autoDispose<DashboardKpis>((ref) as
|
||||
return repository.getKpis();
|
||||
});
|
||||
|
||||
// ── Needs Attention dedicated provider ─────────────────────────────────────
|
||||
final dashboardNeedsAttentionProvider =
|
||||
FutureProvider.autoDispose<NeedsAttentionData>((ref) async {
|
||||
final user = ref.watch(authProvider).value;
|
||||
if (user == null) {
|
||||
throw Exception('User not authenticated');
|
||||
}
|
||||
final repository = ref.watch(dashboardRepositoryProvider);
|
||||
return repository.getNeedsAttention();
|
||||
});
|
||||
|
||||
|
||||
// ── Full paginated activities (used by RecentActivitiesScreen) ─────────────
|
||||
class ActivityQueryParams {
|
||||
final int page;
|
||||
|
||||
@@ -139,6 +139,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
ref.read(activeTabProvider) == 'Dashboard') {
|
||||
ref.invalidate(dashboardDataProvider);
|
||||
ref.invalidate(dashboardKpisProvider);
|
||||
ref.invalidate(dashboardNeedsAttentionProvider);
|
||||
ref.read(notificationsProvider.notifier).refreshNotifications();
|
||||
}
|
||||
});
|
||||
@@ -152,12 +153,14 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
if (!mounted) return;
|
||||
ref.invalidate(dashboardDataProvider);
|
||||
ref.invalidate(dashboardKpisProvider);
|
||||
ref.invalidate(dashboardNeedsAttentionProvider);
|
||||
if (!mounted) return;
|
||||
ref.read(notificationsProvider.notifier).refreshNotifications();
|
||||
if (!mounted) return;
|
||||
await ref.read(dashboardDataProvider.future);
|
||||
}
|
||||
|
||||
|
||||
Color _getNotificationColor(String title) {
|
||||
final t = title.toLowerCase();
|
||||
if (t.contains('student')) return Colors.blue;
|
||||
|
||||
@@ -63,9 +63,9 @@ class NeedsAttentionCard extends ConsumerWidget {
|
||||
final user = ref.watch(authProvider).value;
|
||||
final userRole = user?.role ?? 'teacher';
|
||||
|
||||
final kpisAsync = ref.watch(dashboardKpisProvider);
|
||||
final needsAttentionAsync = ref.watch(dashboardNeedsAttentionProvider);
|
||||
|
||||
if (kpisAsync.isLoading) {
|
||||
if (needsAttentionAsync.isLoading) {
|
||||
return Container(
|
||||
height: 140,
|
||||
decoration: BoxDecoration(
|
||||
@@ -86,7 +86,7 @@ class NeedsAttentionCard extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
if (kpisAsync.hasError) {
|
||||
if (needsAttentionAsync.hasError) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
@@ -113,7 +113,7 @@ class NeedsAttentionCard extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
TextButton(
|
||||
onPressed: () => ref.invalidate(dashboardKpisProvider),
|
||||
onPressed: () => ref.invalidate(dashboardNeedsAttentionProvider),
|
||||
child: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
@@ -122,8 +122,7 @@ class NeedsAttentionCard extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final kpis = kpisAsync.value;
|
||||
final needsAttention = kpis?.needsAttention;
|
||||
final needsAttention = needsAttentionAsync.value;
|
||||
|
||||
// missing requirements from backend needsAttention
|
||||
final List<MissingRequirementGroup> missingGroups = needsAttention?.missingRequirements ?? [];
|
||||
@@ -131,11 +130,10 @@ class NeedsAttentionCard extends ConsumerWidget {
|
||||
missingGroups.fold<int>(0, (sum, g) => sum + g.count);
|
||||
|
||||
// Urgent students needing immediate attention
|
||||
final List<StudentDocCount> urgentStudents = needsAttention?.urgentStudents.isNotEmpty == true
|
||||
? needsAttention!.urgentStudents
|
||||
: (kpis?.bottomStudents.where((s) => s.missingCount > 0).take(5).toList() ?? []);
|
||||
final List<StudentDocCount> urgentStudents = needsAttention?.urgentStudents ?? [];
|
||||
final displayedStudents = urgentStudents.take(3).toList();
|
||||
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark ? AppColors.darkSurfaceCard : Colors.white,
|
||||
|
||||
Reference in new issue
Block a user