Update Saqel Platform: 2026-09-08 17:30:46

This commit is contained in:
Hamza-Ayed
2026-09-08 17:30:46 +03:00
parent 5fd1f32d4c
commit 902cf00152
12 changed files with 333 additions and 9 deletions
@@ -75,6 +75,28 @@ class SuperAdminRepository {
return _decode(response)['data'];
}
Future<dynamic> _post(String path, Map<String, dynamic> body) async {
final response = await http.post(Uri.parse('${AppConfig.apiBaseUrl}$path'), headers: {
...await _headers(), 'Content-Type': 'application/json',
}, body: json.encode(body)).timeout(const Duration(seconds: 20));
return _decode(response)['data'];
}
Future<List<Map<String, dynamic>>> getSchools() async =>
(await _get('/api/super-admin/schools') as List).map((e) => Map<String, dynamic>.from(e as Map)).toList();
Future<List<Map<String, dynamic>>> getStaff() async =>
(await _get('/api/super-admin/staff') as List).map((e) => Map<String, dynamic>.from(e as Map)).toList();
Future<void> saveSchool(Map<String, dynamic> body) async { await _post('/api/super-admin/schools/save', body); }
Future<void> toggleSchool(int id) async { await _post('/api/super-admin/schools/toggle', {'id': id}); }
Future<void> saveStaff(Map<String, dynamic> body) async { await _post('/api/super-admin/staff/save', body); }
Future<void> toggleStaff(int id) async { await _post('/api/super-admin/staff/toggle', {'id': id}); }
Future<List<Map<String, dynamic>>> getDirectorates() async =>
(await _get('/api/super-admin/directorates') as List).map((e) => Map<String, dynamic>.from(e as Map)).toList();
Future<void> saveDirectorate(Map<String, dynamic> body) async { await _post('/api/super-admin/directorates/save', body); }
Future<void> toggleDirectorate(int id) async { await _post('/api/super-admin/directorates/toggle', {'id': id}); }
Future<MacroTelemetryModel> getMacroTelemetry() async =>
MacroTelemetryModel.fromJson(Map<String, dynamic>.from(await _get('/api/super-admin/overview') as Map));
@@ -9,6 +9,7 @@ import 'tabs/macro_radar_tab.dart';
import 'tabs/ai_cluster_tab.dart';
import 'tabs/treasury_cliq_tab.dart';
import 'tabs/security_integrity_tab.dart';
import 'tabs/organization_tab.dart';
/**
* ==============================================================================
@@ -158,6 +159,7 @@ class _SuperAdminShellState extends State<SuperAdminShell> {
alerts: state.alerts,
isKillSwitchActive: state.isEmergencyKillSwitchActive,
),
const OrganizationTab(),
],
);
}
@@ -201,6 +203,11 @@ class _SuperAdminShellState extends State<SuperAdminShell> {
activeIcon: Icon(CupertinoIcons.shield_lefthalf_fill, color: SuperAdminTheme.royalGold),
label: 'النزاهة والأمان',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.building_2_fill),
activeIcon: Icon(CupertinoIcons.building_2_fill, color: SuperAdminTheme.royalGold),
label: 'الهيكل الإداري',
),
],
),
),
@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import '../../../data/repositories/super_admin_repository.dart';
import '../../../core/theme/super_admin_theme.dart';
class OrganizationTab extends StatefulWidget { const OrganizationTab({super.key}); @override State<OrganizationTab> createState()=>_OrganizationTabState(); }
class _OrganizationTabState extends State<OrganizationTab> {
final repo=SuperAdminRepository(); final search=TextEditingController(); String role='all';
List<Map<String,dynamic>> schools=[], staff=[]; bool loading=true;
@override void initState(){super.initState(); _load();}
Future<void> _load() async { setState(()=>loading=true); try { final r=await Future.wait([repo.getSchools(),repo.getStaff()]); if(mounted)setState((){schools=r[0];staff=r[1];loading=false;}); } catch(_){if(mounted)setState(()=>loading=false);}}
void _showMap(Map<String,dynamic> s) { final lat=(s['latitude'] as num?)?.toDouble()??31.9539, lng=(s['longitude'] as num?)?.toDouble()??35.9106; showDialog(context:context,builder:(_)=>AlertDialog(backgroundColor:SuperAdminTheme.surfaceDark,title:Text('${s['name']}',style:const TextStyle(color:Colors.white)),content:SizedBox(width:500,height:380,child:FlutterMap(options:MapOptions(initialCenter:LatLng(lat,lng),initialZoom:13),children:[TileLayer(urlTemplate:'https://tile.openstreetmap.org/{z}/{x}/{y}.png',userAgentPackageName:'com.saqel.super_admin'),MarkerLayer(markers:[Marker(point:LatLng(lat,lng),width:42,height:42,child:const Icon(Icons.location_pin,color:Colors.red,size:42))])])),actions:[TextButton(onPressed:()=>Navigator.pop(context),child:const Text('إغلاق'))])); }
@override Widget build(BuildContext c){ final q=search.text.trim().toLowerCase(); final filtered=staff.where((x)=> (role=='all'||x['role']==role) && (q.isEmpty||'${x['full_name']} ${x['role']}'.toLowerCase().contains(q))).toList(); return RefreshIndicator(onRefresh:_load,child:ListView(padding:const EdgeInsets.all(16),children:[Row(children:[const Expanded(child:Text('إدارة الهيكل الإداري',style:TextStyle(color:Colors.white,fontSize:20,fontWeight:FontWeight.bold))),IconButton(onPressed:_load,icon:const Icon(Icons.refresh,color:SuperAdminTheme.cyberCyan))]),const SizedBox(height:12),TextField(controller:search,onChanged:(_)=>setState((){}),style:const TextStyle(color:Colors.white),decoration:const InputDecoration(labelText:'بحث بالاسم أو الدور',labelStyle:TextStyle(color:Colors.white60),prefixIcon:Icon(Icons.search,color:SuperAdminTheme.cyberCyan))),const SizedBox(height:8),DropdownButton<String>(value:role,isExpanded:true,dropdownColor:SuperAdminTheme.surfaceDark,style:const TextStyle(color:Colors.white),items:const [DropdownMenuItem(value:'all',child:Text('كل الأدوار')),DropdownMenuItem(value:'super_admin',child:Text('Super Admin')),DropdownMenuItem(value:'school_admin',child:Text('مدير مدرسة')),DropdownMenuItem(value:'directorate_admin',child:Text('مدير مديرية')),DropdownMenuItem(value:'supervisor',child:Text('مشرف'))],onChanged:(v)=>setState(()=>role=v??'all')),Text('المدارس (${schools.length})',style:const TextStyle(color:SuperAdminTheme.royalGold,fontWeight:FontWeight.bold)),...schools.map((s)=>ListTile(onTap:()=>_showMap(s),leading:const Icon(Icons.location_on,color:SuperAdminTheme.cyberCyan),title:Text('${s['name']}',style:const TextStyle(color:Colors.white)),subtitle:Text('${s['code']} • ${s['is_active']==1?'نشطة':'موقوفة'} • اضغط لعرض الخريطة',style:const TextStyle(color:Colors.white60)))),const SizedBox(height:12),Text('الحسابات (${filtered.length})',style:const TextStyle(color:SuperAdminTheme.royalGold,fontWeight:FontWeight.bold)),if(loading)const Center(child:CircularProgressIndicator()) else ...filtered.map((s)=>ListTile(leading:const Icon(Icons.badge,color:SuperAdminTheme.cyberCyan),title:Text('${s['full_name']}',style:const TextStyle(color:Colors.white)),subtitle:Text('${s['role']} • ${s['status']}',style:const TextStyle(color:Colors.white60))))])); }
}
+80
View File
@@ -81,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.9"
dart_earcut:
dependency: transitive
description:
name: dart_earcut
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
url: "https://pub.dev"
source: hosted
version: "1.2.0"
device_info_plus:
dependency: "direct main"
description:
@@ -142,6 +150,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -272,6 +288,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.7"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker:
dependency: transitive
description:
@@ -304,6 +328,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
logger:
dependency: transitive
description:
name: logger
sha256: "2a0dc097e7b01d942475bdd552356db2d0f768b05540bd4b2b53f1840f2239a7"
url: "https://pub.dev"
source: hosted
version: "2.8.0"
logging:
dependency: transitive
description:
@@ -336,6 +376,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nested:
dependency: transitive
description:
@@ -432,6 +480,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
provider:
dependency: transitive
description:
@@ -573,6 +637,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
vector_math:
dependency: transitive
description:
@@ -613,6 +685,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.5"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories:
dependency: transitive
description:
+2
View File
@@ -18,6 +18,8 @@ dependencies:
device_info_plus: ^10.1.0
google_fonts: ^6.2.1
intl: ^0.19.0
flutter_map: ^7.0.2
latlong2: ^0.9.1
dev_dependencies:
flutter_test: