Update Saqel Platform: 2026-09-10 12:26:22
This commit is contained in:
@@ -11,12 +11,12 @@ class MacroTelemetryModel {
|
||||
final int totalSchools;
|
||||
final int totalStudents;
|
||||
final int totalTeachers;
|
||||
final double grossMarginPercent;
|
||||
final double treasuryBalanceJod;
|
||||
final double totalCliqInflowJod;
|
||||
final int pendingPayoutsCount;
|
||||
final double r2BandwidthCostSavingsJod;
|
||||
final double uptimePercent;
|
||||
final double? grossMarginPercent;
|
||||
final double? treasuryBalanceJod;
|
||||
final double? totalCliqInflowJod;
|
||||
final int? pendingPayoutsCount;
|
||||
final double? r2BandwidthCostSavingsJod;
|
||||
final double? uptimePercent;
|
||||
|
||||
const MacroTelemetryModel({
|
||||
required this.totalDirectorates,
|
||||
@@ -37,12 +37,12 @@ class MacroTelemetryModel {
|
||||
totalSchools: (json['total_schools'] as num?)?.toInt() ?? 0,
|
||||
totalStudents: (json['total_students'] as num?)?.toInt() ?? 0,
|
||||
totalTeachers: (json['total_teachers'] as num?)?.toInt() ?? 0,
|
||||
grossMarginPercent: (json['gross_margin_percent'] as num?)?.toDouble() ?? 0,
|
||||
treasuryBalanceJod: (json['treasury_balance_jod'] as num?)?.toDouble() ?? 0,
|
||||
totalCliqInflowJod: (json['total_cliq_inflow_jod'] as num?)?.toDouble() ?? 0,
|
||||
pendingPayoutsCount: (json['pending_payouts_count'] as num?)?.toInt() ?? 0,
|
||||
r2BandwidthCostSavingsJod: (json['r2_cost_savings_jod'] as num?)?.toDouble() ?? 0,
|
||||
uptimePercent: (json['uptime_percent'] as num?)?.toDouble() ?? 0,
|
||||
grossMarginPercent: (json['gross_margin_percent'] as num?)?.toDouble(),
|
||||
treasuryBalanceJod: (json['treasury_balance_jod'] as num?)?.toDouble(),
|
||||
totalCliqInflowJod: (json['total_cliq_inflow_jod'] as num?)?.toDouble(),
|
||||
pendingPayoutsCount: (json['pending_payouts_count'] as num?)?.toInt(),
|
||||
r2BandwidthCostSavingsJod: (json['r2_cost_savings_jod'] as num?)?.toDouble(),
|
||||
uptimePercent: (json['uptime_percent'] as num?)?.toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,19 +26,10 @@ class SuperAdminCubit extends Cubit<SuperAdminState> {
|
||||
telemetry: telemetry,
|
||||
aiNodes: aiNodes,
|
||||
alerts: alerts,
|
||||
isEmergencyKillSwitchActive: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(SuperAdminError('فشل تحميل لوحة القيادة السيادية: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
void toggleEmergencyKillSwitch() {
|
||||
if (state is SuperAdminLoaded) {
|
||||
final cur = state as SuperAdminLoaded;
|
||||
emit(cur.copyWith(
|
||||
isEmergencyKillSwitchActive: !cur.isEmergencyKillSwitchActive,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,26 +12,22 @@ class SuperAdminLoaded extends SuperAdminState {
|
||||
final MacroTelemetryModel telemetry;
|
||||
final List<AiClusterNodeModel> aiNodes;
|
||||
final List<SecurityIntegrityAlertModel> alerts;
|
||||
final bool isEmergencyKillSwitchActive;
|
||||
|
||||
const SuperAdminLoaded({
|
||||
required this.telemetry,
|
||||
required this.aiNodes,
|
||||
required this.alerts,
|
||||
this.isEmergencyKillSwitchActive = false,
|
||||
});
|
||||
|
||||
SuperAdminLoaded copyWith({
|
||||
MacroTelemetryModel? telemetry,
|
||||
List<AiClusterNodeModel>? aiNodes,
|
||||
List<SecurityIntegrityAlertModel>? alerts,
|
||||
bool? isEmergencyKillSwitchActive,
|
||||
}) {
|
||||
return SuperAdminLoaded(
|
||||
telemetry: telemetry ?? this.telemetry,
|
||||
aiNodes: aiNodes ?? this.aiNodes,
|
||||
alerts: alerts ?? this.alerts,
|
||||
isEmergencyKillSwitchActive: isEmergencyKillSwitchActive ?? this.isEmergencyKillSwitchActive,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,24 +12,20 @@ class TreasuryLoading extends TreasuryState {}
|
||||
|
||||
class TreasuryLoaded extends TreasuryState {
|
||||
final List<PayoutQueueItemModel> queue;
|
||||
final double totalTreasuryBalanceJod;
|
||||
final double totalApprovedTodayJod;
|
||||
final double? totalTreasuryBalanceJod;
|
||||
|
||||
const TreasuryLoaded({
|
||||
required this.queue,
|
||||
required this.totalTreasuryBalanceJod,
|
||||
required this.totalApprovedTodayJod,
|
||||
});
|
||||
|
||||
TreasuryLoaded copyWith({
|
||||
List<PayoutQueueItemModel>? queue,
|
||||
double? totalTreasuryBalanceJod,
|
||||
double? totalApprovedTodayJod,
|
||||
}) {
|
||||
return TreasuryLoaded(
|
||||
queue: queue ?? this.queue,
|
||||
totalTreasuryBalanceJod: totalTreasuryBalanceJod ?? this.totalTreasuryBalanceJod,
|
||||
totalApprovedTodayJod: totalApprovedTodayJod ?? this.totalApprovedTodayJod,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -42,69 +38,21 @@ class TreasuryCubit extends Cubit<TreasuryState> {
|
||||
Future<void> loadTreasury() async {
|
||||
emit(TreasuryLoading());
|
||||
try {
|
||||
final queue = await repository.getPayoutQueue();
|
||||
final results = await Future.wait([repository.getPayoutQueue(), repository.getMacroTelemetry()]);
|
||||
final queue = results[0] as List<PayoutQueueItemModel>;
|
||||
final telemetry = results[1] as MacroTelemetryModel;
|
||||
emit(TreasuryLoaded(
|
||||
queue: queue,
|
||||
totalTreasuryBalanceJod: 54200.0,
|
||||
totalApprovedTodayJod: 0.0,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(const TreasuryLoaded(
|
||||
queue: [],
|
||||
totalTreasuryBalanceJod: 54200.0,
|
||||
totalApprovedTodayJod: 0.0,
|
||||
totalTreasuryBalanceJod: telemetry.treasuryBalanceJod,
|
||||
));
|
||||
} catch (error) {
|
||||
emit(TreasuryError('تعذر تحميل بيانات الخزينة: $error'));
|
||||
}
|
||||
}
|
||||
|
||||
void approvePayout(int payoutId) {
|
||||
if (state is TreasuryLoaded) {
|
||||
final cur = state as TreasuryLoaded;
|
||||
double approvedAmt = 0.0;
|
||||
}
|
||||
|
||||
final updated = cur.queue.map((item) {
|
||||
if (item.id == payoutId) {
|
||||
approvedAmt = item.amountJod;
|
||||
return PayoutQueueItemModel(
|
||||
id: item.id,
|
||||
teacherName: item.teacherName,
|
||||
cliqAlias: item.cliqAlias,
|
||||
amountJod: item.amountJod,
|
||||
requestedAt: item.requestedAt,
|
||||
status: 'completed',
|
||||
);
|
||||
}
|
||||
return item;
|
||||
}).toList();
|
||||
|
||||
emit(cur.copyWith(
|
||||
queue: updated,
|
||||
totalApprovedTodayJod: cur.totalApprovedTodayJod + approvedAmt,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void approveAllPayouts() {
|
||||
if (state is TreasuryLoaded) {
|
||||
final cur = state as TreasuryLoaded;
|
||||
double sum = 0.0;
|
||||
|
||||
final updated = cur.queue.map((item) {
|
||||
if (item.status == 'queued') sum += item.amountJod;
|
||||
return PayoutQueueItemModel(
|
||||
id: item.id,
|
||||
teacherName: item.teacherName,
|
||||
cliqAlias: item.cliqAlias,
|
||||
amountJod: item.amountJod,
|
||||
requestedAt: item.requestedAt,
|
||||
status: 'completed',
|
||||
);
|
||||
}).toList();
|
||||
|
||||
emit(cur.copyWith(
|
||||
queue: updated,
|
||||
totalApprovedTodayJod: cur.totalApprovedTodayJod + sum,
|
||||
));
|
||||
}
|
||||
}
|
||||
class TreasuryError extends TreasuryState {
|
||||
final String message;
|
||||
const TreasuryError(this.message);
|
||||
}
|
||||
|
||||
@@ -73,28 +73,11 @@ class _SuperAdminShellState extends State<SuperAdminShell> {
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.emeraldGreen.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: SuperAdminTheme.emeraldGreen.withOpacity(0.3)),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(radius: 3, backgroundColor: SuperAdminTheme.emeraldGreen),
|
||||
SizedBox(width: 5),
|
||||
Text('Sovereign Live', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: SuperAdminTheme.emeraldGreen)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
const Text(
|
||||
'المؤسس والمهندس المعماري: حمزة العائد (Hamza Ayed)',
|
||||
'البيانات المعروضة تعتمد على مصادر الخادم المتصلة فقط',
|
||||
style: TextStyle(fontSize: 11.5, color: Colors.white60),
|
||||
),
|
||||
],
|
||||
@@ -155,10 +138,7 @@ class _SuperAdminShellState extends State<SuperAdminShell> {
|
||||
MacroRadarTab(telemetry: state.telemetry),
|
||||
AiClusterTab(nodes: state.aiNodes),
|
||||
const TreasuryCliqTab(),
|
||||
SecurityIntegrityTab(
|
||||
alerts: state.alerts,
|
||||
isKillSwitchActive: state.isEmergencyKillSwitchActive,
|
||||
),
|
||||
SecurityIntegrityTab(alerts: state.alerts),
|
||||
const OrganizationTab(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,284 +1,29 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/theme/super_admin_theme.dart';
|
||||
import '../../../data/models/super_admin_models.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL SOVEREIGN COMMAND - LOCAL AI & GPU CLUSTER TAB
|
||||
* ==============================================================================
|
||||
*
|
||||
* رصد فوري لعناقيد الذكاء الاصطناعي السيادي المحلي:
|
||||
* - نماذج Qwen 2.5-VL (تحليل ومراجعة كراسات الحصص والمحتوى المرئي)
|
||||
* - نموذج DeepSeek-R1 (المحاكمة المنطقية والاستدلال التربوي والردود السقراطية)
|
||||
* - قياس استهلاك الذاكرة الرسومية VRAM وزمن الاستجابة (Latency ms)
|
||||
* - ضمان السيادة الرقمية التامة (Zero-Egress Sovereignty) بدون تسريب أي بيانات للخارج.
|
||||
*/
|
||||
class AiClusterTab extends StatelessWidget {
|
||||
final List<AiClusterNodeModel> nodes;
|
||||
|
||||
const AiClusterTab({super.key, required this.nodes});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Sovereign AI Sovereignty Moat Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF0F172A), Color(0xFF1E293B)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: SuperAdminTheme.cyberCyan.withOpacity(0.4)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: SuperAdminTheme.cyberCyan.withOpacity(0.12),
|
||||
blurRadius: 18,
|
||||
offset: const Offset(0, 6),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.cyberCyan, size: 22),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'عنقود الذكاء الاصطناعي السيادي (Local Inference)',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.emeraldGreen.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'100% Zero-Egress',
|
||||
style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: SuperAdminTheme.emeraldGreen),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'يعمل هذا العنقود داخل البنية التحتية الوطنية المغلقة. يتم معالجة تسجيلات الحصص المدرسية وكراسات التقييم باستخدام نماذج Qwen 2.5-VL و DeepSeek-R1 دون خروج أي بايت إلى خوادم أجنبية.',
|
||||
style: TextStyle(fontSize: 12.5, color: Colors.white70, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
_buildQuickMetric('إجمالي العقد النشطة', '${nodes.length} عقد مخصصة', CupertinoIcons.layers_alt_fill),
|
||||
const SizedBox(width: 12),
|
||||
_buildQuickMetric('متوسط زمن الاستجابة', '120 ms', CupertinoIcons.bolt_fill),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Widget build(BuildContext context) => ListView(padding: const EdgeInsets.all(16), children: [
|
||||
const Text('عقد الذكاء الاصطناعي', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 6),
|
||||
const Text('تعرض هذه الشاشة فقط العقد المعرّفة من مصدر تشغيل الخادم. لا تستنتج مكان المعالجة أو زمن الاستجابة أو سياسة البيانات عند غياب telemetry حقيقية.', style: TextStyle(color: Colors.white60, height: 1.4)),
|
||||
const SizedBox(height: 16),
|
||||
if (nodes.isEmpty) const Padding(padding: EdgeInsets.all(24), child: Center(child: Text('لا توجد بيانات عقد متاحة من الخادم.', style: TextStyle(color: Colors.white60)))) else ...nodes.map(_node),
|
||||
]);
|
||||
|
||||
// Nodes List Section Title
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.circle_grid_hex_fill, color: SuperAdminTheme.royalGold, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'حالة عقد المعالجة الرسومية (GPU Nodes Telemetry)',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Render Nodes
|
||||
...nodes.map((node) => _buildNodeCard(node)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Inference Model Architecture Guide
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.white10),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'توزيع مهام النماذج المتخصصة:',
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildModelArchitectureRow(
|
||||
'Qwen 2.5-VL 7B / 72B',
|
||||
'تحليل كراسات الطلاب، تدقيق فيديوهات الحصص (MIT 20-25 Min Limit)، واستخراج الرسوم التوضيحية.',
|
||||
SuperAdminTheme.cyberCyan,
|
||||
),
|
||||
const Divider(color: Colors.white10, height: 20),
|
||||
_buildModelArchitectureRow(
|
||||
'DeepSeek-R1 (Distill / Dense)',
|
||||
'الاستدلال الرياضي المتقدم، المحاكمة المنطقية، وبناء الحوارات السقراطية لغرف التساؤلات المدرسية.',
|
||||
SuperAdminTheme.imperialPurple,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickMetric(String label, String value, IconData icon) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: SuperAdminTheme.cyberCyan),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 11, color: Colors.white54)),
|
||||
const SizedBox(height: 2),
|
||||
Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNodeCard(AiClusterNodeModel node) {
|
||||
final double vramPercentage = (node.gpuVramUsageGb / node.gpuTotalVramGb).clamp(0.0, 1.0);
|
||||
final Color progressColor = vramPercentage > 0.85
|
||||
? SuperAdminTheme.crimsonRed
|
||||
: (vramPercentage > 0.65 ? SuperAdminTheme.royalGold : SuperAdminTheme.emeraldGreen);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: node.status == 'active_online' ? SuperAdminTheme.emeraldGreen : SuperAdminTheme.royalGold,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (node.status == 'active_online' ? SuperAdminTheme.emeraldGreen : SuperAdminTheme.royalGold).withOpacity(0.6),
|
||||
blurRadius: 6,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
node.nodeName,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black45,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Text(
|
||||
'${node.latencyMs} ms',
|
||||
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600, color: SuperAdminTheme.cyberCyan),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${node.modelName} • ${node.role}',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// VRAM Progress
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('استهلاك VRAM للبطاقة الرسومية', style: TextStyle(fontSize: 11.5, color: Colors.white54)),
|
||||
Text(
|
||||
'${node.gpuVramUsageGb.toStringAsFixed(1)} GB / ${node.gpuTotalVramGb.toStringAsFixed(0)} GB (${(vramPercentage * 100).toStringAsFixed(0)}%)',
|
||||
style: TextStyle(fontSize: 11.5, fontWeight: FontWeight.w600, color: progressColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: vramPercentage,
|
||||
minHeight: 6,
|
||||
backgroundColor: Colors.white10,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(progressColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildModelArchitectureRow(String model, String role, Color accentColor) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(CupertinoIcons.checkmark_seal_fill, size: 16, color: accentColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(model, style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.bold, color: accentColor)),
|
||||
const SizedBox(height: 2),
|
||||
Text(role, style: const TextStyle(fontSize: 11.5, color: Colors.white60, height: 1.4)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
Widget _node(AiClusterNodeModel node) {
|
||||
final usableVram = node.gpuTotalVramGb > 0;
|
||||
final ratio = usableVram ? (node.gpuVramUsageGb / node.gpuTotalVramGb).clamp(0.0, 1.0) : 0.0;
|
||||
return Card(color: SuperAdminTheme.surfaceCard, child: Padding(padding: const EdgeInsets.all(16), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(node.nodeName, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4), Text('${node.modelName} • ${node.role} • ${node.status}', style: const TextStyle(color: Colors.white60)),
|
||||
if (usableVram) ...[const SizedBox(height: 12), LinearProgressIndicator(value: ratio, color: SuperAdminTheme.cyberCyan), const SizedBox(height: 4), Text('${node.gpuVramUsageGb.toStringAsFixed(1)} / ${node.gpuTotalVramGb.toStringAsFixed(1)} GB', style: const TextStyle(color: Colors.white60))],
|
||||
if (node.latencyMs > 0) Padding(padding: const EdgeInsets.only(top: 8), child: Text('${node.latencyMs} ms', style: const TextStyle(color: Colors.white60))),
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,228 +1,26 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../core/theme/super_admin_theme.dart';
|
||||
import '../../../data/models/super_admin_models.dart';
|
||||
|
||||
class MacroRadarTab extends StatelessWidget {
|
||||
final MacroTelemetryModel telemetry;
|
||||
|
||||
const MacroRadarTab({super.key, required this.telemetry});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Unit Economics & Gross Margin Sovereign Moat Banner
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF1E1B4B), Color(0xFF0F172A)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: SuperAdminTheme.imperialPurple.withOpacity(0.5)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: SuperAdminTheme.imperialPurple.withOpacity(0.15),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.royalGold, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'حماية هوامش الربح ووحدة الاقتصاد (Unit Economics)',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.emeraldGreen.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'هامش ربح ${telemetry.grossMarginPercent}%',
|
||||
style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w900, color: SuperAdminTheme.emeraldGreen),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Text(
|
||||
'وفر معمارية صَقِل السيادية مقارنة بالمنصات التقليدية:\n'
|
||||
'• خفض تكلفة الخرائط والبث بمقدار 0.30 دولار لكل طالب.\n'
|
||||
'• استبدال اشتراكات السحابة الأجنبية بالذكاء الاصطناعي المحلي (Qwen/DeepSeek).\n'
|
||||
'• التخزين السيادي عبر كودك البث المشفر لتوفير آلاف الدنانير شهرياً.',
|
||||
style: TextStyle(fontSize: 12.5, color: Color(0xFFCBD5E1), height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Widget build(BuildContext context) => ListView(padding: const EdgeInsets.all(16), children: [
|
||||
const Text('المؤشرات المتاحة من الخادم', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18)),
|
||||
const SizedBox(height: 6),
|
||||
const Text('لا تعرض هذه الشاشة إلا عدادات قاعدة البيانات. القياسات المالية والتشغيلية تبقى غير متاحة حتى ربط مصادرها الموثقة.', style: TextStyle(color: Colors.white60, height: 1.4)),
|
||||
const SizedBox(height: 16),
|
||||
_grid(),
|
||||
const SizedBox(height: 18),
|
||||
_unavailable('المالية والتسوية', telemetry.treasuryBalanceJod == null ? 'غير متاحة: مزود الدفع ودفتر التسوية غير مربوطين.' : '${telemetry.treasuryBalanceJod} د.أ'),
|
||||
_unavailable('مراقبة الاستقرار', telemetry.uptimePercent == null ? 'غير متاحة: لا يوجد مزود مراقبة متصل.' : '${telemetry.uptimePercent}%'),
|
||||
]);
|
||||
|
||||
// Core Metric Tiles (2x2 Grid)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _metricCard(
|
||||
title: 'إجمالي المدارس',
|
||||
value: '${telemetry.totalSchools} مدرسة',
|
||||
subtitle: '43 ثقافة عسكرية + مجمعات خاصة',
|
||||
icon: CupertinoIcons.building_2_fill,
|
||||
color: SuperAdminTheme.cyberCyan,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _metricCard(
|
||||
title: 'الطلبة المسجلون',
|
||||
value: '${telemetry.totalStudents}',
|
||||
subtitle: '19,350 برقم وطني مشفر',
|
||||
icon: CupertinoIcons.person_3_fill,
|
||||
color: SuperAdminTheme.imperialPurple,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _metricCard(
|
||||
title: 'الكادر التعليمي',
|
||||
value: '${telemetry.totalTeachers} معلماً',
|
||||
subtitle: 'معتمدون ومصنفون بالجدارة',
|
||||
icon: CupertinoIcons.star_circle_fill,
|
||||
color: SuperAdminTheme.royalGold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _metricCard(
|
||||
title: 'استقرار النظام',
|
||||
value: '${telemetry.uptimePercent}%',
|
||||
subtitle: 'خوادم سيادية بلا انقطاع',
|
||||
icon: CupertinoIcons.checkmark_shield_fill,
|
||||
color: SuperAdminTheme.emeraldGreen,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Directorate Matrix Overview
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: SuperAdminTheme.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'المظلات المركزية المعتمدة في المنظومة 🏛️',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_directorateRow(
|
||||
name: 'مديرية التربية والتعليم والثقافة العسكرية',
|
||||
schools: 43,
|
||||
students: 19350,
|
||||
badge: 'عقد مؤسسي سيادي',
|
||||
color: SuperAdminTheme.emeraldGreen,
|
||||
),
|
||||
const Divider(color: SuperAdminTheme.border, height: 20),
|
||||
_directorateRow(
|
||||
name: 'مجمعات المدارس الخاصة المعتمدة (النمو السحابي)',
|
||||
schools: 12,
|
||||
students: 4200,
|
||||
badge: 'سوق صَقِل المفتوح',
|
||||
color: SuperAdminTheme.cyberCyan,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _metricCard({
|
||||
required String title,
|
||||
required String value,
|
||||
required String subtitle,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: SuperAdminTheme.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 24),
|
||||
const SizedBox(height: 10),
|
||||
Text(title, style: const TextStyle(fontSize: 12, color: Color(0xFF94A3B8))),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: Colors.white)),
|
||||
const SizedBox(height: 2),
|
||||
Text(subtitle, style: const TextStyle(fontSize: 10.5, color: Color(0xFF64748B))),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _directorateRow({
|
||||
required String name,
|
||||
required int schools,
|
||||
required int students,
|
||||
required String badge,
|
||||
required Color color,
|
||||
}) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(name, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: Colors.white)),
|
||||
const SizedBox(height: 3),
|
||||
Text('$schools مدرسة · $students طالباً', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(badge, style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700, color: color)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
Widget _grid() => GridView.count(crossAxisCount: 2, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), childAspectRatio: 1.55, children: [_metric('المديريات', telemetry.totalDirectorates, CupertinoIcons.building_2_fill), _metric('المدارس', telemetry.totalSchools, CupertinoIcons.building_2_fill), _metric('الطلبة', telemetry.totalStudents, CupertinoIcons.person_3_fill), _metric('المعلمون', telemetry.totalTeachers, CupertinoIcons.person_badge_plus_fill)]);
|
||||
Widget _metric(String label, int value, IconData icon) => Card(color: SuperAdminTheme.surfaceCard, child: Padding(padding: const EdgeInsets.all(14), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Icon(icon, color: SuperAdminTheme.cyberCyan), const Spacer(), Text(label, style: const TextStyle(color: Colors.white60)), Text('$value', style: const TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold))])));
|
||||
Widget _unavailable(String label, String value) => Padding(padding: const EdgeInsets.only(bottom: 10), child: ListTile(tileColor: SuperAdminTheme.surfaceCard, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text(label, style: const TextStyle(color: Colors.white)), subtitle: Text(value, style: const TextStyle(color: Colors.white60))));
|
||||
}
|
||||
|
||||
@@ -1,257 +1,24 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/theme/super_admin_theme.dart';
|
||||
import '../../../data/models/super_admin_models.dart';
|
||||
import '../../../logic/cubits/super_admin_cubit.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL SOVEREIGN COMMAND - CYBER & EXAM INTEGRITY TAB
|
||||
* ==============================================================================
|
||||
*
|
||||
* رادار النزاهة الأكاديمية والأمن السيبراني السيادي:
|
||||
* - تشفير الأرقام الوطنية AES-256-GCM وحماية السجلات من أي تسريب
|
||||
* - مراقبة فحص الترفع الصفي والتحقق من صلاحيات الدخول المؤسسي
|
||||
* - إنذارات فورية لمحاولات الغش وحل الاختبارات بسرعة مستحيلة فلكياً (Impossible Speed)
|
||||
* - زر الإغلاق السيادي الطارئ (Emergency Sovereign Kill-Switch).
|
||||
*/
|
||||
class SecurityIntegrityTab extends StatelessWidget {
|
||||
final List<SecurityIntegrityAlertModel> alerts;
|
||||
final bool isKillSwitchActive;
|
||||
|
||||
const SecurityIntegrityTab({
|
||||
super.key,
|
||||
required this.alerts,
|
||||
required this.isKillSwitchActive,
|
||||
});
|
||||
const SecurityIntegrityTab({super.key, required this.alerts});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Emergency Kill-Switch Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isKillSwitchActive ? SuperAdminTheme.crimsonRed.withOpacity(0.2) : SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isKillSwitchActive ? SuperAdminTheme.crimsonRed : Colors.white12,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isKillSwitchActive ? CupertinoIcons.exclamationmark_octagon_fill : CupertinoIcons.shield_fill,
|
||||
color: isKillSwitchActive ? SuperAdminTheme.crimsonRed : SuperAdminTheme.emeraldGreen,
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isKillSwitchActive ? 'وضع العزل الطارئ نشط (Kill-Switch Active)' : 'منظومة الدفاع السيادية تعمل بنجاح',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isKillSwitchActive ? SuperAdminTheme.crimsonRed : Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
isKillSwitchActive
|
||||
? 'تم إيقاف استلام الطلبات الخارجية وتجميد جلسات الامتحانات احترازياً.'
|
||||
: 'جميع جلسات الاختبارات مشفرة وتخضع لرقابة النزاهة اللحظية.',
|
||||
style: const TextStyle(fontSize: 11.5, color: Colors.white60),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CupertinoSwitch(
|
||||
value: isKillSwitchActive,
|
||||
activeColor: SuperAdminTheme.crimsonRed,
|
||||
onChanged: (val) {
|
||||
context.read<SuperAdminCubit>().toggleEmergencyKillSwitch();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Widget build(BuildContext context) => ListView(padding: const EdgeInsets.all(16), children: [
|
||||
const Text('الأمن والنزاهة', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
_notice('لا توجد مراقبة أمنية موثقة متصلة حالياً. لذلك لا تعني القائمة الفارغة أن النظام آمن أو أن الإنذارات تساوي صفراً.'),
|
||||
const SizedBox(height: 18),
|
||||
const Text('الإنذارات المسجلة', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 10),
|
||||
if (alerts.isEmpty) const Text('مصدر إنذارات النزاهة غير متاح حالياً.', style: TextStyle(color: Colors.white60)) else ...alerts.map(_alert),
|
||||
]);
|
||||
|
||||
// Sovereign Encryption & Integrity Badges
|
||||
Row(
|
||||
children: [
|
||||
_buildSecurityStatBadge(
|
||||
'تشفير الأرقام الوطنية',
|
||||
'AES-256-GCM',
|
||||
'صفر تسريب بيانات',
|
||||
SuperAdminTheme.cyberCyan,
|
||||
CupertinoIcons.lock_shield_fill,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_buildSecurityStatBadge(
|
||||
'بوابة الحصص والصفوف',
|
||||
'Grade-Gate 100%',
|
||||
'فصل تام للصفوف 8 - 12',
|
||||
SuperAdminTheme.emeraldGreen,
|
||||
CupertinoIcons.checkmark_seal_fill,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Security Incidents Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.shield_slash_fill, color: SuperAdminTheme.royalGold, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'إنذارات النزاهة السيادية اللحظية',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.crimsonRed.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${alerts.length} إنذارات',
|
||||
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: SuperAdminTheme.crimsonRed),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Alerts List
|
||||
...alerts.map((alert) => _buildAlertCard(alert)),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Audit Log Integrity Explanation
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: Colors.white10),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.eye_solid, size: 16, color: SuperAdminTheme.cyberCyan),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'خوارزمية كشف الشذوذ الأكاديمي (Anomaly Detection):',
|
||||
style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'تقوم المنظومة بمقارنة زمن حل الطالب لكل سؤال رياضي بالحد الأدنى المعرفي. إذا تم تقديم اختبار يحتوي 30 مسألة تفاضل في أقل من 12 ثانية، يُصنف الاختبار فوراً كـ Anomaly ويتم تجميد العلامة لتدقيق المعلم والمشرف.',
|
||||
style: TextStyle(fontSize: 11.5, color: Colors.white60, height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSecurityStatBadge(String title, String value, String subtitle, Color color, IconData icon) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: color.withOpacity(0.3)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, color: color, size: 20),
|
||||
const SizedBox(height: 10),
|
||||
Text(value, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: color)),
|
||||
const SizedBox(height: 2),
|
||||
Text(title, style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.w600, color: Colors.white)),
|
||||
const SizedBox(height: 2),
|
||||
Text(subtitle, style: const TextStyle(fontSize: 10, color: Colors.white54)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAlertCard(SecurityIntegrityAlertModel alert) {
|
||||
final bool isCritical = alert.severity == 'critical';
|
||||
final Color alertColor = isCritical ? SuperAdminTheme.crimsonRed : SuperAdminTheme.royalGold;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: alertColor.withOpacity(0.35)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isCritical ? CupertinoIcons.exclamationmark_triangle_fill : CupertinoIcons.bell_fill,
|
||||
color: alertColor,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
alert.title,
|
||||
style: const TextStyle(fontSize: 13.5, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
alert.timeAgo,
|
||||
style: const TextStyle(fontSize: 11, color: Colors.white38),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'الموقع / المدرسة: ${alert.schoolName}',
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: SuperAdminTheme.cyberCyan),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
alert.details,
|
||||
style: const TextStyle(fontSize: 11.5, color: Colors.white70, height: 1.4),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget _notice(String text) => Container(padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: SuperAdminTheme.royalGold.withOpacity(.12), borderRadius: BorderRadius.circular(14)), child: Row(children: [const Icon(CupertinoIcons.exclamationmark_triangle, color: SuperAdminTheme.royalGold), const SizedBox(width: 10), Expanded(child: Text(text, style: const TextStyle(color: Colors.white70, height: 1.4)))]));
|
||||
Widget _alert(SecurityIntegrityAlertModel item) => Card(color: SuperAdminTheme.surfaceCard, child: ListTile(title: Text(item.title, style: const TextStyle(color: Colors.white)), subtitle: Text('${item.schoolName}\n${item.details}', style: const TextStyle(color: Colors.white60)), trailing: Text(item.severity, style: const TextStyle(color: SuperAdminTheme.royalGold))));
|
||||
}
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../../core/theme/super_admin_theme.dart';
|
||||
import '../../../data/models/super_admin_models.dart';
|
||||
import '../../../logic/cubits/treasury_cubit.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL SOVEREIGN COMMAND - TREASURY & CLIQ PAYOUT QUEUE TAB
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة الخزينة المركزية السيادية والموافقة الفورية على دفعات المعلمين عبر CliQ:
|
||||
* - رصيد الخزينة الإجمالي (54,200 دينار أردني)
|
||||
* - موافقة جماعية بنقرة واحدة (1-Click Mass Payout Approval)
|
||||
* - معالجة فورية عبر نمط Siro-Engine بدون عمولات وسيطة (Zero-Intermediary Fee)
|
||||
* - سجل تفصيلي لطلبات السحب المعلقة والمكتملة.
|
||||
*/
|
||||
/// Read-only until a payment provider and an auditable settlement ledger exist.
|
||||
class TreasuryCliqTab extends StatelessWidget {
|
||||
const TreasuryCliqTab({super.key});
|
||||
|
||||
@@ -23,299 +14,27 @@ class TreasuryCliqTab extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TreasuryCubit, TreasuryState>(
|
||||
builder: (context, state) {
|
||||
if (state is TreasuryLoading) {
|
||||
return const Center(
|
||||
child: CupertinoActivityIndicator(color: SuperAdminTheme.cyberCyan),
|
||||
);
|
||||
}
|
||||
|
||||
if (state is TreasuryLoaded) {
|
||||
final pendingItems = state.queue.where((item) => item.status == 'queued').toList();
|
||||
final double pendingSum = pendingItems.fold(0.0, (acc, item) => acc + item.amountJod);
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Sovereign Treasury Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF064E3B), Color(0xFF0F172A)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: SuperAdminTheme.emeraldGreen.withOpacity(0.5)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: SuperAdminTheme.emeraldGreen.withOpacity(0.18),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.money_dollar_circle_fill, color: SuperAdminTheme.emeraldGreen, size: 22),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'الخزينة السيادية المركزية (Sovereign Treasury)',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black38,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'CliQ Direct Rail',
|
||||
style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: SuperAdminTheme.emeraldGreen),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'${state.totalTreasuryBalanceJod.toStringAsFixed(2)} د.أ',
|
||||
style: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'صافي السيولة النقدية المودعة والمحمية في الحساب المصرفي المركزي الموحد.',
|
||||
style: TextStyle(fontSize: 12, color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
_buildStatBadge('المعلق للسحب', '${pendingSum.toStringAsFixed(2)} د.أ', SuperAdminTheme.royalGold),
|
||||
const SizedBox(width: 10),
|
||||
_buildStatBadge('المصروف اليوم', '${state.totalApprovedTodayJod.toStringAsFixed(2)} د.أ', SuperAdminTheme.cyberCyan),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 1-Click Mass Approval Action
|
||||
if (pendingItems.isNotEmpty)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: SuperAdminTheme.royalGold.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.checkmark_shield_fill, color: SuperAdminTheme.royalGold, size: 26),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'يوجد ${pendingItems.length} طلبات سحب معلقة بقيمة ${pendingSum.toStringAsFixed(2)} د.أ',
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const Text(
|
||||
'يمكنك اعتماد وإرسال التحويلات فورياً عبر شبكة كليك المركزية.',
|
||||
style: TextStyle(fontSize: 11.5, color: Colors.white60),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
CupertinoButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
color: SuperAdminTheme.royalGold,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onPressed: () {
|
||||
context.read<TreasuryCubit>().approveAllPayouts();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم اعتماد وصرف جميع مستحقات المعلمين بنجاح عبر شبكة CliQ!'),
|
||||
backgroundColor: SuperAdminTheme.emeraldGreen,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
'اعتماد الكل',
|
||||
style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.bold, color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Payout Queue Section Header
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.arrow_right_arrow_left, color: SuperAdminTheme.cyberCyan, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'طابور سحوبات المعلمين (Payout Queue)',
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'${state.queue.length} عمليات',
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white54),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// List Items
|
||||
...state.queue.map((item) => _buildPayoutCard(context, item)),
|
||||
|
||||
if (state.queue.isEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(28),
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
'لا توجد طلبات سحب حالياً في الطابور.',
|
||||
style: TextStyle(color: Colors.white38, fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
if (state is TreasuryLoading) return const Center(child: CupertinoActivityIndicator(color: SuperAdminTheme.cyberCyan));
|
||||
if (state is TreasuryError) return Center(child: Padding(padding: const EdgeInsets.all(24), child: Text(state.message, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70))));
|
||||
if (state is! TreasuryLoaded) return const SizedBox.shrink();
|
||||
final queued = state.queue.where((item) => item.status == 'queued').toList();
|
||||
final queuedAmount = queued.fold<double>(0, (sum, item) => sum + item.amountJod);
|
||||
return ListView(padding: const EdgeInsets.all(16), children: [
|
||||
_notice('الخزينة للقراءة فقط', 'لا يوجد مزود دفع أو دفتر تسوية موثق متصل حالياً؛ لا يمكن اعتماد أو إرسال أي سحب من هذه الشاشة.'),
|
||||
const SizedBox(height: 16),
|
||||
_metric('رصيد قابل للتسوية', state.totalTreasuryBalanceJod == null ? 'غير متاح' : '${state.totalTreasuryBalanceJod!.toStringAsFixed(2)} د.أ'),
|
||||
const SizedBox(height: 16),
|
||||
Text('طلبات السحب المسجلة (${state.queue.length})', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 6),
|
||||
Text('قيمة الطلبات بحالة queued: ${queuedAmount.toStringAsFixed(2)} د.أ', style: const TextStyle(color: Colors.white60)),
|
||||
const SizedBox(height: 12),
|
||||
if (state.queue.isEmpty) const Padding(padding: EdgeInsets.all(24), child: Center(child: Text('لا توجد سجلات سحب متاحة.', style: TextStyle(color: Colors.white60)))) else ...state.queue.map(_payout),
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatBadge(String label, String value, Color color) {
|
||||
return Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.35),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 11, color: Colors.white54)),
|
||||
const SizedBox(height: 2),
|
||||
Text(value, style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: color)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPayoutCard(BuildContext context, PayoutQueueItemModel item) {
|
||||
final bool isQueued = item.status == 'queued';
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isQueued ? SuperAdminTheme.royalGold.withOpacity(0.3) : Colors.white10,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: isQueued ? SuperAdminTheme.royalGold.withOpacity(0.15) : SuperAdminTheme.emeraldGreen.withOpacity(0.15),
|
||||
child: Icon(
|
||||
isQueued ? CupertinoIcons.clock_fill : CupertinoIcons.checkmark_alt,
|
||||
color: isQueued ? SuperAdminTheme.royalGold : SuperAdminTheme.emeraldGreen,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.teacherName,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'اسم مستعار كليك: ${item.cliqAlias} • ${item.requestedAt}',
|
||||
style: const TextStyle(fontSize: 11.5, color: Colors.white54),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${item.amountJod.toStringAsFixed(2)} د.أ',
|
||||
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (isQueued)
|
||||
CupertinoButton(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
color: SuperAdminTheme.emeraldGreen,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
minSize: 26,
|
||||
onPressed: () {
|
||||
context.read<TreasuryCubit>().approvePayout(item.id);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('تم اعتماد تحويل ${item.amountJod} د.أ إلى المعلم ${item.teacherName} عبر CliQ'),
|
||||
backgroundColor: SuperAdminTheme.emeraldGreen,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
'اعتماد',
|
||||
style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: Colors.black),
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: SuperAdminTheme.emeraldGreen.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Text(
|
||||
'مكتمل ومحوّل',
|
||||
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: SuperAdminTheme.emeraldGreen),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget _notice(String title, String body) => Container(padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: SuperAdminTheme.royalGold.withOpacity(.12), borderRadius: BorderRadius.circular(14), border: Border.all(color: SuperAdminTheme.royalGold.withOpacity(.4))), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(color: SuperAdminTheme.royalGold, fontWeight: FontWeight.bold)), const SizedBox(height: 6), Text(body, style: const TextStyle(color: Colors.white70, height: 1.4))]));
|
||||
Widget _metric(String label, String value) => Container(padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: SuperAdminTheme.surfaceCard, borderRadius: BorderRadius.circular(14)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(label, style: const TextStyle(color: Colors.white60)), const SizedBox(height: 4), Text(value, style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold))]));
|
||||
Widget _payout(PayoutQueueItemModel item) => Card(color: SuperAdminTheme.surfaceCard, child: ListTile(leading: const Icon(CupertinoIcons.money_dollar_circle, color: SuperAdminTheme.cyberCyan), title: Text(item.teacherName, style: const TextStyle(color: Colors.white)), subtitle: Text('${item.cliqAlias} • ${item.requestedAt} • ${item.status}', style: const TextStyle(color: Colors.white60)), trailing: Text('${item.amountJod.toStringAsFixed(2)} د.أ', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user