feat(student-profile): implement Priority 1 operational hub with tabs, inline document workflows, and audit history

This commit is contained in:
ssbg04 committed 2026-10-09 08:45:05 +08:00
1 parent 0e99b73989
commit 489e87834e
6 files changed
+1613 -276

No files matched your search

@@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
import '../../core/network/api_constants.dart';
import '../entities/student_model.dart';
import '../entities/dashboard_models.dart' show RecentActivity;
/// Paginated student list returned from the API.
class StudentPage {
@@ -581,4 +582,25 @@ class StudentRepository {
throw Exception(msg);
}
}
// ----------------------------------------------------------------
// Fetch Student Audit / Activity Log
// ----------------------------------------------------------------
Future<List<RecentActivity>> getStudentActivities(int studentId) async {
try {
final options = await _getAuthOptions();
final response = await _dio.get(
'/students/$studentId/activities',
options: options,
);
final list = (response.data['activities'] as List?) ?? [];
return list
.map((a) => RecentActivity.fromJson(a as Map<String, dynamic>))
.toList();
} on DioException catch (e) {
final msg =
e.response?.data?['message'] ?? 'Failed to fetch student activities.';
throw Exception(msg);
}
}
}
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../domain/repositories/document_repository.dart';
import '../../domain/entities/document_requirement_model.dart';
import '../../domain/entities/folder_model.dart';
import '../../domain/entities/document_model.dart';
import 'student_provider.dart';
final documentRepositoryProvider = Provider<DocumentRepository>((ref) {
@@ -146,7 +147,7 @@ final documentPageProvider = FutureProvider.autoDispose<DocumentPage>((
// Student-specific documents provider
// ============================================================
final studentDocumentsProvider = FutureProvider.family
.autoDispose<List<dynamic>, int>((ref, studentId) async {
.autoDispose<List<DocumentModel>, int>((ref, studentId) async {
final repo = ref.read(documentRepositoryProvider);
return repo.getDocumentsByStudent(studentId);
});
@@ -5,6 +5,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../core/network/api_constants.dart';
import '../../domain/repositories/student_repository.dart';
import '../../domain/entities/student_model.dart';
import '../../domain/entities/dashboard_models.dart' show RecentActivity;
// ============================================================
// Academic Years Model
@@ -68,6 +69,15 @@ final studentDetailProvider = FutureProvider.family
return repo.getStudentById(studentId);
});
// ============================================================
// Student Activities / Audit Trail Provider
// ============================================================
final studentActivitiesProvider = FutureProvider.family
.autoDispose<List<RecentActivity>, int>((ref, studentId) async {
final repo = ref.read(studentRepositoryProvider);
return repo.getStudentActivities(studentId);
});
// ============================================================
// Query params state — drives what the list shows
// ============================================================
@@ -86,7 +86,7 @@ class _DocumentationScreenState extends State<DocumentationScreen> {
elevation: 1,
titleSpacing: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
icon: const Icon(Icons.arrow_back, color: Colors.white),
tooltip: 'Back to Application',
onPressed: () => Navigator.of(context).pop(),
),
@@ -130,12 +130,12 @@ class _DocumentationScreenState extends State<DocumentationScreen> {
),
actions: [
IconButton(
icon: const Icon(Icons.open_in_browser_rounded, size: 20),
icon: const Icon(Icons.open_in_browser_rounded, size: 20, color: Colors.white),
tooltip: 'Open in External Browser',
onPressed: _openInBrowser,
),
IconButton(
icon: const Icon(Icons.close, size: 20),
icon: const Icon(Icons.close, size: 20, color: Colors.white),
tooltip: 'Close Documentation',
onPressed: () => Navigator.of(context).pop(),
),
File diff suppressed because it is too large. Load diff