import 'dart:convert'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:file_picker/file_picker.dart'; import '../../services/directorate_api_service.dart'; /// School dashboard deliberately renders only server-provided records. /// Uploads, exam dispatch, parent messaging, and roster imports stay unavailable /// here until each action has an auditable production workflow. class SchoolPrincipalScreen extends StatefulWidget { const SchoolPrincipalScreen({super.key}); @override State createState() => _SchoolPrincipalScreenState(); } class _SchoolPrincipalScreenState extends State { Future>? _dashboard; bool _busy = false; String? _operationMessage; @override void initState() { super.initState(); _reload(); } void _reload() => setState(() => _dashboard = DirectorateApiService.fetchSchoolDashboard()); Future _pickVideo() async { final picked = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: const ['mp4', 'mov', 'webm'], withData: true); return picked?.files.isNotEmpty == true ? picked!.files.single : null; } Future _run(String label, Future Function() operation) async { setState(() { _busy = true; _operationMessage = null; }); try { final result = await operation(); if (mounted) setState(() => _operationMessage = result); _reload(); } catch (error) { if (mounted) setState(() => _operationMessage = '$label تعذر: $error'); } finally { if (mounted) setState(() => _busy = false); } } Future _recordLesson(int schoolId, List> teachers) async { if (teachers.isEmpty) { setState(() => _operationMessage = 'لا يوجد معلمون متاحون من الخادم.'); return; } final title = TextEditingController(); var selected = teachers.first; final confirmed = await showDialog(context: context, builder: (dialogContext) => StatefulBuilder(builder: (_, setDialogState) => AlertDialog( title: const Text('رفع حصة مرصودة'), content: Column(mainAxisSize: MainAxisSize.min, children: [DropdownButton>(value: selected, isExpanded: true, items: teachers.map((teacher) => DropdownMenuItem(value: teacher, child: Text(teacher['name']?.toString() ?? 'معلم'))).toList(), onChanged: (value) { if (value != null) setDialogState(() => selected = value); }), TextField(controller: title, decoration: const InputDecoration(labelText: 'عنوان الحصة'))]), actions: [TextButton(onPressed: () => Navigator.pop(dialogContext, false), child: const Text('إلغاء')), FilledButton(onPressed: () => Navigator.pop(dialogContext, true), child: const Text('اختيار ملف'))], ))); if (confirmed != true || title.text.trim().isEmpty) return; final file = await _pickVideo(); if (file == null) return; await _run('رفع الحصة', () async { final result = await DirectorateApiService.recordLesson(schoolId: schoolId, teacherId: (selected['id'] as num).toInt(), subject: selected['subject']?.toString() ?? '', gradeLevel: selected['grade']?.toString() ?? 'grade_10', lessonTitle: title.text.trim(), durationMinutes: 0, filePath: file.path, fileBytes: file.bytes, fileName: file.name); return 'تم تسجيل الحصة بحالة ${result.status}.'; }); } Future _uploadPanorama(int sessionId) async { final file = await _pickVideo(); if (file == null) return; await _run('رفع العينة', () async { await DirectorateApiService.uploadPanoramicSample(sessionId: sessionId, filePath: file.path, fileBytes: file.bytes, fileName: file.name); return 'تم حفظ العينة وربطها بالجلسة.'; }); } Future _importRoster(int schoolId) async { final picked = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: const ['json'], withData: true); final file = picked?.files.isNotEmpty == true ? picked!.files.single : null; if (file?.bytes == null) return; final decoded = json.decode(utf8.decode(file!.bytes!)); if (decoded is! List) throw StateError('ملف الكشف يجب أن يحتوي قائمة JSON من السجلات.'); final records = decoded.whereType().map((row) => Map.from(row)).toList(); await _run('استيراد الكشف', () async { final result = await DirectorateApiService.importSchoolRoster(schoolId: schoolId, records: records); return 'استُورد ${result['imported_count'] ?? 0} سجل؛ الفاشل ${result['failed_count'] ?? 0}.'; }); } @override Widget build(BuildContext context) => Scaffold( backgroundColor: const Color(0xFF070B12), appBar: AppBar( backgroundColor: const Color(0xFF0E1626), title: const Text('لوحة المدرسة'), actions: [IconButton(onPressed: _reload, icon: const Icon(CupertinoIcons.refresh))], ), body: FutureBuilder>( future: _dashboard, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) return const Center(child: CupertinoActivityIndicator()); if (snapshot.hasError) return Center(child: Padding(padding: const EdgeInsets.all(24), child: Text('تعذر تحميل بيانات المدرسة: ${snapshot.error}', textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70)))); final data = snapshot.data ?? const {}; final school = data['school'] is Map ? Map.from(data['school'] as Map) : const {}; final teachers = data['teachers'] is List ? (data['teachers'] as List).whereType().map((item) => Map.from(item)).toList() : const >[]; final exams = data['active_exams'] is List ? (data['active_exams'] as List).whereType().map((item) => Map.from(item)).toList() : const >[]; return RefreshIndicator(onRefresh: () async => _reload(), child: ListView(padding: const EdgeInsets.all(16), children: [ Text(school['name']?.toString() ?? 'مدرسة غير محددة', style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text('${school['governorate'] ?? 'الموقع غير متاح'} • الطلبة: ${school['student_count'] ?? 'غير متاح'} • المعلمون: ${school['teacher_count'] ?? 'غير متاح'}', style: const TextStyle(color: Colors.white60)), const SizedBox(height: 20), _operations(school, teachers, exams), const SizedBox(height: 20), const Text('المعلمون', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 8), if (teachers.isEmpty) const Text('لا توجد بيانات معلمين متاحة من الخادم.', style: TextStyle(color: Colors.white60)) else ...teachers.map(_teacher), const SizedBox(height: 20), const Text('الامتحانات الفعالة', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 8), if (exams.isEmpty) const Text('لا توجد امتحانات فعالة في البيانات المستلمة.', style: TextStyle(color: Colors.white60)) else ...exams.map(_exam), ])); }, ), ); Widget _notice(String title, String body) => Container(padding: const EdgeInsets.all(14), decoration: BoxDecoration(color: const Color(0xFF4338CA).withOpacity(.2), borderRadius: BorderRadius.circular(12)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(color: Color(0xFFC4B5FD), fontWeight: FontWeight.bold)), const SizedBox(height: 5), Text(body, style: const TextStyle(color: Colors.white70, height: 1.4))])); Widget _operations(Map school, List> teachers, List> exams) { final schoolId = (school['id'] as num?)?.toInt(); if (schoolId == null) return _notice('الإجراءات غير متاحة', 'لم يعِد الخادم هوية مدرسة صالحة.'); return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _notice('إجراءات خادمية فعلية', 'كل زر يرسل طلباً حقيقياً للخادم. لا توجد موافقات أو نتائج محلية.'), if (_operationMessage != null) Padding(padding: const EdgeInsets.only(top: 8), child: Text(_operationMessage!, style: const TextStyle(color: Color(0xFFC4B5FD)))), const SizedBox(height: 8), Wrap(spacing: 8, runSpacing: 8, children: [ OutlinedButton(onPressed: _busy ? null : () => _recordLesson(schoolId, teachers), child: const Text('رفع حصة مرصودة')), OutlinedButton(onPressed: _busy ? null : () => _run('إرسال التقارير', () async { final result = await DirectorateApiService.dispatchParentReports(schoolId: schoolId); return 'أُرسل ${result['total_dispatched'] ?? 0} تقرير؛ فشل ${result['failed_count'] ?? 0}.'; }), child: const Text('إرسال تقارير الأولياء')), OutlinedButton(onPressed: _busy ? null : () => _importRoster(schoolId), child: const Text('استيراد كشف JSON')), ]), if (exams.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 8), child: Wrap(spacing: 8, runSpacing: 8, children: exams.where((exam) => exam['id'] is num).map((exam) => OutlinedButton(onPressed: _busy ? null : () => _run('تفعيل الامتحان', () async { await DirectorateApiService.pushExamToLab(examId: (exam['id'] as num).toInt(), schoolId: schoolId); return 'تم تفعيل الامتحان للمختبر المسجل.'; }), child: Text('تفعيل: ${exam['title'] ?? 'امتحان'}'))).toList())), if (exams.any((exam) => exam['session_id'] is num)) Padding(padding: const EdgeInsets.only(top: 8), child: OutlinedButton(onPressed: _busy ? null : () => _uploadPanorama((exams.firstWhere((exam) => exam['session_id'] is num)['session_id'] as num).toInt()), child: const Text('رفع عينة بانورامية'))), const SizedBox(height: 20), ]); } Widget _teacher(Map item) => Card(color: const Color(0xFF0F172A), child: ListTile(title: Text(item['name']?.toString() ?? 'معلم بدون اسم', style: const TextStyle(color: Colors.white)), subtitle: Text('${item['subject'] ?? 'المبحث غير متاح'} • ${item['quota_status'] ?? 'الحالة غير متاحة'}', style: const TextStyle(color: Colors.white60)))); Widget _exam(Map item) => Card(color: const Color(0xFF0F172A), child: ListTile(title: Text(item['title']?.toString() ?? 'امتحان بدون عنوان', style: const TextStyle(color: Colors.white)), subtitle: Text('${item['subject'] ?? 'المبحث غير متاح'} • ${item['status'] ?? 'الحالة غير متاحة'}', style: const TextStyle(color: Colors.white60)))); }