Files
TIS_RMS/frontend/lib/domain/repositories/setup_repository.dart
T
ssbg04 bd2a8acbfe feat(settings): clean academic setup buttons and rename screen to Academic & Class Management
- Update screen, settings card, and dashboard titles to 'Academic & Class Management'

- Clean and balance Academic Structure toolbar for mobile and desktop

- Remove redundant buttons on section cards in favor of interactive adviser chip

- Modernize teacher action button to OutlinedButton.icon 'Sections'

- Add responsive bulk academic structure import support across backend and frontend
2026-09-27 02:05:47 +08:00

348 lines
10 KiB
Dart

import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../core/network/api_constants.dart';
import '../entities/setup_models.dart';
class SetupRepository {
final Dio _dio = ApiConstants.createDio();
final FlutterSecureStorage _storage = const FlutterSecureStorage();
Future<Options> _getAuthOptions() async {
final token = await _storage.read(key: 'jwt_token');
return Options(headers: {'Authorization': 'Bearer $token'});
}
String _extractErrorMessage(DioException e, String fallback) {
final data = e.response?.data;
if (data is Map && data['message'] != null) {
return data['message'].toString();
}
if (data is String && data.trim().isNotEmpty) {
final cleaned = data
.replaceAll(RegExp(r'<[^>]*>'), ' ')
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
if (cleaned.isNotEmpty && cleaned.length < 200) {
return cleaned;
}
}
return fallback;
}
// ==========================================
// ACADEMIC YEARS
// ==========================================
Future<List<AcademicYearModel>> getAcademicYears() async {
try {
final options = await _getAuthOptions();
final response = await _dio.get(
'/setup/academic-years',
options: options,
);
return (response.data as List)
.map(
(item) => AcademicYearModel.fromJson(item as Map<String, dynamic>),
)
.toList();
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to fetch academic years.'));
}
}
Future<void> createAcademicYear({
required String yearRange,
required String status,
String? startDate,
String? endDate,
}) async {
try {
final options = await _getAuthOptions();
await _dio.post(
'/setup/academic-years',
options: options,
data: {
'yearRange': yearRange,
'status': status,
if (startDate != null) 'startDate': startDate,
if (endDate != null) 'endDate': endDate,
},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to create academic year.'));
}
}
Future<void> updateAcademicYear({
required int id,
required String yearRange,
required String status,
String? startDate,
String? endDate,
}) async {
try {
final options = await _getAuthOptions();
await _dio.put(
'/setup/academic-years/$id',
options: options,
data: {
'yearRange': yearRange,
'status': status,
if (startDate != null) 'startDate': startDate,
if (endDate != null) 'endDate': endDate,
},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to update academic year.'));
}
}
Future<Map<String, dynamic>> checkAutoGraduation() async {
try {
final options = await _getAuthOptions();
final res = await _dio.post(
'/setup/academic-years/check-auto-graduation',
options: options,
);
return res.data as Map<String, dynamic>;
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to check auto-graduation.'));
}
}
Future<Map<String, dynamic>> checkAutoArchive() async {
try {
final options = await _getAuthOptions();
final res = await _dio.post(
'/setup/academic-years/check-auto-archive',
options: options,
);
return res.data as Map<String, dynamic>;
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to check auto-archive.'));
}
}
Future<void> deleteAcademicYear(int id) async {
try {
final options = await _getAuthOptions();
await _dio.delete('/setup/academic-years/$id', options: options);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to delete academic year.'));
}
}
// ==========================================
// SECTIONS
// ==========================================
Future<List<SectionModel>> getAllSections() async {
try {
final options = await _getAuthOptions();
final response = await _dio.get('/setup/sections', options: options);
return (response.data as List)
.map((item) => SectionModel.fromJson(item as Map<String, dynamic>))
.toList();
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to fetch sections.'));
}
}
Future<List<SectionModel>> getSectionsByYear(int yearId) async {
try {
final options = await _getAuthOptions();
final response = await _dio.get(
'/setup/academic-years/$yearId/sections',
options: options,
);
return (response.data as List)
.map((item) => SectionModel.fromJson(item as Map<String, dynamic>))
.toList();
} on DioException catch (e) {
throw Exception(
_extractErrorMessage(e, 'Failed to fetch sections for academic year.'),
);
}
}
Future<void> createSection({
required String name,
required int gradeLevel,
required int academicYearId,
int? teacherId,
}) async {
try {
final options = await _getAuthOptions();
await _dio.post(
'/setup/sections',
options: options,
data: {
'name': name,
'gradeLevel': gradeLevel,
'academicYearId': academicYearId,
if (teacherId != null) 'teacherId': teacherId,
},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to create section.'));
}
}
Future<Map<String, dynamic>> bulkCreateAcademicStructure(
List<Map<String, dynamic>> rows,
) async {
try {
final options = await _getAuthOptions();
final response = await _dio.post(
'/setup/bulk-academic-structure',
options: options,
data: {'rows': rows},
);
if (response.data is Map) {
return Map<String, dynamic>.from(response.data as Map);
}
return {'message': response.data?.toString() ?? 'Bulk setup completed.'};
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to import academic structure.'));
}
}
Future<void> updateSection({
required int id,
required String name,
required int gradeLevel,
required int academicYearId,
int? teacherId,
}) async {
try {
final options = await _getAuthOptions();
await _dio.put(
'/setup/sections/$id',
options: options,
data: {
'name': name,
'gradeLevel': gradeLevel,
'academicYearId': academicYearId,
'teacherId': teacherId,
},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to update section.'));
}
}
Future<void> setSectionAdviser({
required int sectionId,
int? teacherId,
}) async {
try {
final options = await _getAuthOptions();
await _dio.put(
'/setup/sections/$sectionId/adviser',
options: options,
data: {'teacherId': teacherId},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to update section adviser.'));
}
}
Future<void> deleteSection(int id) async {
try {
final options = await _getAuthOptions();
await _dio.delete('/setup/sections/$id', options: options);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to delete section.'));
}
}
// ==========================================
// GRADE LEVELS
// ==========================================
Future<List<GradeLevelModel>> getGradeLevels() async {
try {
final options = await _getAuthOptions();
final response = await _dio.get('/setup/grade-levels', options: options);
return (response.data as List)
.map((item) => GradeLevelModel.fromJson(item as Map<String, dynamic>))
.toList();
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to fetch grade levels.'));
}
}
Future<void> createGradeLevel({
required int level,
required String name,
}) async {
try {
final options = await _getAuthOptions();
await _dio.post(
'/setup/grade-levels',
options: options,
data: {'level': level, 'name': name},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to create grade level.'));
}
}
Future<void> updateGradeLevel({
required int id,
required int level,
required String name,
}) async {
try {
final options = await _getAuthOptions();
await _dio.put(
'/setup/grade-levels/$id',
options: options,
data: {'level': level, 'name': name},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to update grade level.'));
}
}
Future<void> deleteGradeLevel(int id) async {
try {
final options = await _getAuthOptions();
await _dio.delete('/setup/grade-levels/$id', options: options);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to delete grade level.'));
}
}
// ==========================================
// TEACHER SECTIONS
// ==========================================
Future<List<SectionModel>> getTeacherSections(int teacherId) async {
try {
final options = await _getAuthOptions();
final response = await _dio.get(
'/users/$teacherId/sections',
options: options,
);
return (response.data as List)
.map((item) => SectionModel.fromJson(item as Map<String, dynamic>))
.toList();
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to fetch teacher sections.'));
}
}
Future<void> updateTeacherSections({
required int teacherId,
required List<int> sectionIds,
}) async {
try {
final options = await _getAuthOptions();
await _dio.post(
'/users/$teacherId/sections',
options: options,
data: {'sectionIds': sectionIds},
);
} on DioException catch (e) {
throw Exception(_extractErrorMessage(e, 'Failed to update teacher sections.'));
}
}
}