Files
saqel/apps/admin_app/lib/screens/directorate_command_screen.dart
T

693 lines
26 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../models/directorate_models.dart';
import '../services/directorate_api_service.dart';
class DirectorateCommandScreen extends StatefulWidget {
const DirectorateCommandScreen({super.key});
@override
State<DirectorateCommandScreen> createState() =>
_DirectorateCommandScreenState();
}
class _DirectorateCommandScreenState extends State<DirectorateCommandScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
bool _isLoading = true;
Map<String, dynamic>? _data;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_loadDashboard();
}
Future<void> _loadDashboard() async {
setState(() => _isLoading = true);
final res = await DirectorateApiService.fetchDirectorateDashboard();
if (mounted) {
setState(() {
_data = res;
_isLoading = false;
});
}
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(
backgroundColor: Color(0xFF070B12),
body: Center(
child:
CupertinoActivityIndicator(color: Color(0xFF8B5CF6), radius: 18),
),
);
}
final dir = _data?['directorate'] ?? {};
final schools = (_data?['schools'] as List? ?? [])
.map((s) => SchoolItem.fromJson(s))
.toList();
final anomalies = (_data?['anomalies'] as List? ?? [])
.map((a) => ExamAnomalyAlert.fromJson(a))
.toList();
final radar = _data?['radar'] ?? {};
return Scaffold(
backgroundColor: const Color(0xFF070B12),
appBar: AppBar(
backgroundColor: const Color(0xFF0E1626),
elevation: 0,
centerTitle: false,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Color(0xFF10B981),
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
Text(
dir['name'] ?? 'مديرية الثقافة العسكرية',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: Colors.white),
),
],
),
const SizedBox(height: 2),
const Text(
'لوحة القائد الأعلى للرقابة والسيادة الرقمية (43 مدرسة في المملكة)',
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
),
],
),
bottom: TabBar(
controller: _tabController,
indicatorColor: const Color(0xFF8B5CF6),
indicatorWeight: 3,
labelColor: Colors.white,
unselectedLabelColor: const Color(0xFF64748B),
labelStyle:
const TextStyle(fontWeight: FontWeight.w700, fontSize: 13.5),
tabs: const [
Tab(
icon: Icon(CupertinoIcons.chart_bar_alt_fill, size: 18),
text: 'الرؤية الكلية والمدارس',
),
Tab(
icon: Icon(CupertinoIcons.star_circle_fill, size: 18),
text: 'رادار المعلمين والتسييل',
),
Tab(
icon: Icon(CupertinoIcons.shield_lefthalf_fill, size: 18),
text: 'نزاهة الامتحانات الموحدة',
),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildMacroOverviewTab(dir, schools),
_buildTeachersRadarTab(radar),
_buildExamIntegrityTab(anomalies),
],
),
);
}
// ---------------------------------------------------------------------------
// TAB 1: Macro Directorate Metrics & 43 Schools Matrix
// ---------------------------------------------------------------------------
Widget _buildMacroOverviewTab(
Map<String, dynamic> dir, List<SchoolItem> schools) {
return SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Commander KPI Ribbon
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF1E1B4B), Color(0xFF0F172A)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border:
Border.all(color: const Color(0xFF6366F1).withOpacity(0.4)),
),
child: Column(
children: [
Row(
children: [
Expanded(
child: _kpiCell(
'إجمالي المدارس',
'${dir['total_schools'] ?? 43} مدرسة',
CupertinoIcons.building_2_fill,
const Color(0xFF818CF8),
),
),
Expanded(
child: _kpiCell(
'الطلبة المسجلون',
'${dir['total_students'] ?? 19350}',
CupertinoIcons.person_3_fill,
const Color(0xFF38BDF8),
),
),
],
),
const SizedBox(height: 14),
Row(
children: [
Expanded(
child: _kpiCell(
'معلمو الثقافة العسكرية',
'${dir['total_teachers'] ?? 812} معلم',
CupertinoIcons.person_badge_plus_fill,
const Color(0xFFFBBF24),
),
),
Expanded(
child: _kpiCell(
'نسبة كوتة الحصص',
'${dir['compliance_rate'] ?? 94.5}%',
CupertinoIcons.check_mark_circled_solid,
const Color(0xFF34D399),
),
),
],
),
],
),
),
const SizedBox(height: 20),
// Schools Weight Tier Breakdown
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'توزيع مدارس الثقافة العسكرية الـ 43 حسب تصنيف الوزن:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
Text(
'إجمالي العقد: 20,000 د.أ',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: const Color(0xFFA78BFA)),
),
],
),
const SizedBox(height: 12),
// School List Cards
...schools.map((s) => Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: _statusColor(s.status).withOpacity(0.15),
borderRadius: BorderRadius.circular(10),
),
child: Icon(CupertinoIcons.building_2_fill,
color: _statusColor(s.status), size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
s.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(height: 4),
Row(
children: [
Text(
s.governorate,
style: const TextStyle(
fontSize: 12, color: Color(0xFF94A3B8)),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 1),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(4),
),
child: Text(
s.weightTierArabic,
style: const TextStyle(
fontSize: 11, color: Color(0xFFA78BFA)),
),
),
],
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${s.compliance}% التزام',
style: TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w700,
color: _statusColor(s.status),
),
),
const SizedBox(height: 4),
Text(
'${s.studentCount} طالب · ${s.annualFee.toInt()} د.أ/سنة',
style: const TextStyle(
fontSize: 11, color: Color(0xFF64748B)),
),
],
),
],
),
)),
],
),
);
}
// ---------------------------------------------------------------------------
// TAB 2: Teachers Radar & Marketplace Monetization
// ---------------------------------------------------------------------------
Widget _buildTeachersRadarTab(Map<String, dynamic> radar) {
final topTeachers = (radar['top_teachers'] as List? ?? []);
final needingSupport = (radar['needing_support'] as List? ?? []);
return SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Monetization Model Header
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF14532D), Color(0xFF0F172A)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border:
Border.all(color: const Color(0xFF22C55E).withOpacity(0.4)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.money_dollar_circle_fill,
color: Color(0xFF4ADE80), size: 22),
SizedBox(width: 8),
Text(
'سوق تسييل المعلمين المتميزين (شراكة الأرباح)',
style: TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w700,
color: Colors.white),
),
],
),
const SizedBox(height: 8),
const Text(
'المعلمون الحاصلون على تقييم 95%+ يتم منحهم اعتماد "معلم صَقِل المعتمد" وتُباع شروحاتهم خارج الثقافة العسكرية مع توزيع العائد: 55% للمعلم، 15% للمديرية، 30% لصَقِل.',
style: TextStyle(
fontSize: 12.5, color: Color(0xFFCBD5E1), height: 1.5),
),
],
),
),
const SizedBox(height: 20),
// Top Teachers List
const Text(
'🌟 أفضل المعلمين المرشحين للتسييل والاعتماد الرسمي:',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 12),
...topTeachers.map((t) => Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: const Color(0xFF10B981).withOpacity(0.4)),
),
child: Row(
children: [
const CircleAvatar(
radius: 20,
backgroundColor: Color(0xFF065F46),
child: Icon(CupertinoIcons.star_fill,
color: Color(0xFF34D399), size: 18),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
t['name'] ?? '',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(height: 2),
Text(
'${t['subject']} · ${t['school']}',
style: const TextStyle(
fontSize: 12, color: Color(0xFF94A3B8)),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF10B981).withOpacity(0.2),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${t['score']}% تقييم AI',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w800,
color: Color(0xFF34D399)),
),
),
],
),
)),
const SizedBox(height: 18),
// Early Intervention Radar
const Text(
'⚠️ رادار الإنذار والتوجيه المبكر (معالجة القصور قبل التوجيهي):',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 12),
...needingSupport.map((ns) => Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: const Color(0xFFEF4444).withOpacity(0.3)),
),
child: Row(
children: [
const CircleAvatar(
radius: 20,
backgroundColor: Color(0xFF7F1D1D),
child: Icon(CupertinoIcons.exclamationmark_triangle_fill,
color: Color(0xFFF87171), size: 18),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
ns['name'] ?? '',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(height: 2),
Text(
'${ns['school']} · ${ns['issue']}',
style: const TextStyle(
fontSize: 12, color: Color(0xFFF87171)),
),
],
),
),
Text(
'${ns['score']}%',
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xFFF87171)),
),
],
),
)),
],
),
);
}
// ---------------------------------------------------------------------------
// TAB 3: Kingdom Exam Integrity & Statistical Fraud Detection
// ---------------------------------------------------------------------------
Widget _buildExamIntegrityTab(List<ExamAnomalyAlert> anomalies) {
return SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Live Exam Integrity Header
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF1E293B), Color(0xFF0F172A)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border:
Border.all(color: const Color(0xFF0284C7).withOpacity(0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.shield_fill,
color: Color(0xFF38BDF8), size: 20),
SizedBox(width: 8),
Text(
'غرفة عمليات نزاهة الامتحانات الموحدة',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
color: Colors.white),
),
],
),
const SizedBox(height: 8),
const Text(
'مراقبة لحظية لنحو 19,000 طالب يخوضون الامتحان الموحد المتزامن عبر 43 مدرسة، برصد فوري للسرعة المستحيلة وتكتل الأخطاء.',
style: TextStyle(
fontSize: 12.5, color: Color(0xFF94A3B8), height: 1.5),
),
],
),
),
const SizedBox(height: 20),
const Text(
'تنبيهات الشذوذ الإحصائي وشبهات التواطؤ:',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 12),
if (anomalies.isEmpty)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: const Center(
child: Text(
'✅ لا توجد أي تنبيهات شذوذ حالياً · كافة المدارس تعمل بانضباط تام',
style: TextStyle(color: Color(0xFF10B981), fontSize: 13.5),
),
),
)
else
...anomalies.map((anm) => Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1E1B2E),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: const Color(0xFFEF4444).withOpacity(0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Icon(
CupertinoIcons
.exclamationmark_triangle_fill,
color: Color(0xFFEF4444),
size: 18),
const SizedBox(width: 8),
Text(
anm.schoolName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
],
),
Text(
anm.timestamp,
style: const TextStyle(
fontSize: 11.5, color: Color(0xFF94A3B8)),
),
],
),
const SizedBox(height: 8),
Text(
anm.description,
style: const TextStyle(
fontSize: 13,
color: Color(0xFFFCA5A5),
height: 1.4),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'🎥 جاري فتح العينة البانورامية المسجلة (16.5 MB) لتلك الدقيقة للتحقق البصري المباشر'),
backgroundColor: Color(0xFF0284C7),
),
);
},
icon: const Icon(CupertinoIcons.play_circle_fill,
size: 16),
label: const Text(
'عرض العينة البانورامية المسجلة في هذه الدقيقة 👁️',
style: TextStyle(fontSize: 12.5),
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0284C7),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
),
],
),
)),
],
),
);
}
Widget _kpiCell(
String title, String value, IconData icon, Color accentColor) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: accentColor.withOpacity(0.15),
shape: BoxShape.circle,
),
child: Icon(icon, color: accentColor, size: 20),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 11.5, color: Color(0xFF94A3B8)),
),
const SizedBox(height: 2),
Text(
value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
color: Colors.white),
),
],
)
],
);
}
Color _statusColor(String status) {
switch (status) {
case 'excellent':
return const Color(0xFF10B981);
case 'warning_under_quota':
return const Color(0xFFEF4444);
case 'good':
default:
return const Color(0xFF38BDF8);
}
}
}