// fuel_stations_page.dart — إدارة محطات الوقود الشريكة // // ‏الشاشة تعمل كاملةً حتى والمنتج مُطفأ: قرار المالك أن تُجهَّز الأمور // ‏وتُبنى الشاشات، ولا يُشغَّل المنتج إلا بعد إتمام الاتفاق مع سلسلة // ‏المحطات. لذلك اللافتة العلوية تقول الحالة صراحةً بدل إخفائها. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '../../../controller/fuel/fuel_admin_controller.dart'; import '../../../controller/fuel/fuel_admin_models.dart'; import 'fuel_drawdowns_page.dart'; import 'fuel_station_form.dart'; class FuelStationsPageView extends StatefulWidget { const FuelStationsPageView({super.key}); @override State createState() => _FuelStationsPageViewState(); } class _FuelStationsPageViewState extends State { final _searchCtrl = TextEditingController(); @override void initState() { super.initState(); Get.put(FuelAdminController()); } @override void dispose() { _searchCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return GetBuilder( builder: (c) => Scaffold( backgroundColor: cs.surface, body: Column( children: [ Container( padding: const EdgeInsets.fromLTRB(16, 50, 16, 12), child: Row( children: [ GestureDetector( onTap: () => Get.back(), child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), border: Border.all(color: cs.outline), ), child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurfaceVariant, size: 16), ), ), const SizedBox(width: 12), Text('محفظة الوقود — المحطات', style: TextStyle( color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.w700)), const Spacer(), IconButton( icon: Icon(Icons.receipt_long_rounded, color: cs.tertiary), tooltip: 'حركة القسائم', onPressed: () => Get.to(() => const FuelDrawdownsPageView()), ), IconButton( icon: Icon(Icons.add, color: cs.primary), tooltip: 'إضافة محطة', onPressed: () => _openForm(c, null), ), ], ), ), _productBanner(c, cs), Padding( padding: const EdgeInsets.all(12), child: TextField( controller: _searchCtrl, style: TextStyle(color: cs.onSurface), onSubmitted: (v) { c.searchQuery = v; c.fetchStations(); }, decoration: InputDecoration( hintText: 'ابحث باسم المحطة أو السلسلة أو العنوان...', hintStyle: TextStyle(color: cs.onSurfaceVariant), prefixIcon: Icon(Icons.search, color: cs.onSurfaceVariant), filled: true, fillColor: cs.surfaceContainerHighest, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), ), ), Expanded( child: c.isLoading ? Center(child: CircularProgressIndicator(color: cs.primary)) : c.stations.isEmpty ? _emptyState(cs) : RefreshIndicator( onRefresh: c.fetchStations, child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: c.stations.length, itemBuilder: (_, i) => _stationCard(c, c.stations[i], cs), ), ), ), ], ), ), ); } /// ‏لافتة الحالة. «مُجهَّز وغير مُشغَّل» حالة مقصودة لا عطل — وإخفاؤها /// ‏يجعل من يعرض المنصة يظنّ المنتج حياً، أو يظنّه معطوباً. Widget _productBanner(FuelAdminController c, ColorScheme cs) { final active = c.productActive; final color = active ? cs.primary : cs.tertiary; return Container( margin: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(14), border: Border.all(color: color.withValues(alpha: 0.4)), ), child: Row( children: [ Icon(active ? Icons.check_circle_rounded : Icons.pause_circle_rounded, color: color, size: 20), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(active ? 'المنتج مُشغَّل' : 'مُجهَّز — غير مُشغَّل', style: TextStyle( color: color, fontWeight: FontWeight.w700, fontSize: 14)), const SizedBox(height: 2), Text( active ? 'السائقون المؤهَّلون يستطيعون التفعيل وإصدار القسائم' : 'الشاشة تعمل للتجهيز والعرض. لا تفعيل ولا قسائم للسائقين.', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11), ), ], ), ), Switch( value: active, activeThumbColor: cs.primary, onChanged: (v) => _confirmToggle(c, v), ), ], ), ); } /// ‏تأكيد قبل التشغيل لا بعده. التشغيل يفتح باب إقراض حقيقي بمال /// ‏الشركة، ومفتاح يُقلب بالخطأ في عرضٍ أمام مستثمر ليس أمراً هيّناً. Future _confirmToggle(FuelAdminController c, bool value) async { final cs = Theme.of(context).colorScheme; final ok = await Get.dialog( AlertDialog( backgroundColor: cs.surfaceContainerHighest, title: Text(value ? 'تشغيل محفظة الوقود؟' : 'إيقاف محفظة الوقود؟', style: TextStyle(color: cs.onSurface)), content: Text( value ? 'سيتمكّن السائقون المؤهَّلون من التزوّد بالدَّين فوراً. لا تُشغّله قبل إتمام الاتفاق مع المحطات.' : 'سيتوقّف التفعيل وإصدار القسائم. الالتزامات القائمة ودفترها لا تتأثر.', style: TextStyle(color: cs.onSurfaceVariant), ), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('إلغاء')), TextButton( onPressed: () => Get.back(result: true), child: Text(value ? 'تشغيل' : 'إيقاف', style: TextStyle(color: value ? cs.primary : cs.error)), ), ], ), ); if (ok != true) return; final done = await c.toggleProduct(value); if (!done && c.lastError != null) { Get.snackbar('تعذّر التنفيذ', c.lastError!, backgroundColor: cs.error.withValues(alpha: 0.15)); } } Widget _emptyState(ColorScheme cs) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.local_gas_station_rounded, size: 48, color: cs.onSurfaceVariant), const SizedBox(height: 12), Text('لا توجد محطات بعد', style: TextStyle(color: cs.onSurface, fontSize: 16)), const SizedBox(height: 6), Text('أضف أول محطة شريكة لتجهيز المنتج', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ); Widget _stationCard(FuelAdminController c, FuelStation s, ColorScheme cs) { final statusColor = s.isActive ? cs.primary : cs.onSurfaceVariant; return Card( color: cs.surfaceContainerHighest, margin: const EdgeInsets.only(bottom: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), title: Row( children: [ Expanded( child: Text(s.nameAr, style: TextStyle( color: cs.onSurface, fontWeight: FontWeight.bold)), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Text(s.isActive ? 'فعّالة' : 'موقوفة', style: TextStyle(color: statusColor, fontSize: 11)), ), ], ), subtitle: Padding( padding: const EdgeInsets.only(top: 6), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( [ if (s.chainName != null && s.chainName!.isNotEmpty) s.chainName!, if (s.city != null && s.city!.isNotEmpty) s.city!, ].join(' · '), style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12), ), const SizedBox(height: 6), Row( children: [ Icon(Icons.percent_rounded, size: 13, color: cs.tertiary), const SizedBox(width: 2), Text('خصم ${s.discountPercent}', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(width: 12), Icon(Icons.local_gas_station, size: 13, color: cs.onSurfaceVariant), const SizedBox(width: 2), Text('${s.redeemCount} صرف', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(width: 12), Text('${s.redeemTotal}', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ], ), ), trailing: PopupMenuButton( color: cs.surfaceContainerHighest, icon: Icon(Icons.more_vert, color: cs.onSurfaceVariant, size: 18), onSelected: (v) async { if (v == 'edit') { _openForm(c, s); } else if (v == 'rotate') { await _rotateKey(c, s); } }, itemBuilder: (_) => [ PopupMenuItem( value: 'edit', child: Text('تعديل', style: TextStyle(color: cs.onSurface))), PopupMenuItem( value: 'rotate', child: Text('توليد مفتاح جديد', style: TextStyle(color: cs.error))), ], ), onTap: () => _openForm(c, s), ), ); } Future _openForm(FuelAdminController c, FuelStation? station) async { final apiKey = await Get.to( () => FuelStationForm(station: station), ); if (apiKey != null && apiKey.isNotEmpty) { await _showKey(apiKey, station?.nameAr ?? 'المحطة الجديدة'); } } Future _rotateKey(FuelAdminController c, FuelStation s) async { final cs = Theme.of(context).colorScheme; final ok = await Get.dialog( AlertDialog( backgroundColor: cs.surfaceContainerHighest, title: Text('توليد مفتاح جديد؟', style: TextStyle(color: cs.onSurface)), content: Text( 'المفتاح الحالي لـ«${s.nameAr}» سيتوقّف فوراً، ولن تستطيع المحطة تأكيد أي قسيمة حتى تستلم الجديد.', style: TextStyle(color: cs.onSurfaceVariant), ), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('إلغاء')), TextButton( onPressed: () => Get.back(result: true), child: Text('توليد', style: TextStyle(color: cs.error))), ], ), ); if (ok != true) return; final key = await c.rotateKey(s.id); if (key != null) { await _showKey(key, s.nameAr); } else if (c.lastError != null) { Get.snackbar('تعذّر التوليد', c.lastError!, backgroundColor: cs.error.withValues(alpha: 0.15)); } } /// ‏المفتاح يظهر مرة واحدة — مخزَّن مُهشَّراً على الخادم فلا سبيل /// ‏لاستعادته. الحوار لا يُغلق باللمس خارجه: إغلاقه بالخطأ قبل النسخ /// ‏يعني محطة بلا مفتاح ودورة توليد جديدة. Future _showKey(String key, String stationName) async { final cs = Theme.of(context).colorScheme; await Get.dialog( barrierDismissible: false, AlertDialog( backgroundColor: cs.surfaceContainerHighest, title: Text('مفتاح $stationName', style: TextStyle(color: cs.onSurface, fontSize: 16)), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: cs.surface, borderRadius: BorderRadius.circular(10), border: Border.all(color: cs.outline), ), child: SelectableText(key, style: TextStyle( color: cs.onSurface, fontSize: 12, fontFamily: 'monospace')), ), const SizedBox(height: 10), Text( 'انسخه الآن وسلّمه للمحطة. لن يظهر مرة أخرى — المفتاح مخزَّن مشفّراً ولا يمكن استرجاعه.', style: TextStyle(color: cs.error, fontSize: 11), ), ], ), actions: [ TextButton( onPressed: () { Clipboard.setData(ClipboardData(text: key)); Get.snackbar('نُسخ', 'المفتاح في الحافظة'); }, child: Text('نسخ', style: TextStyle(color: cs.primary)), ), TextButton( onPressed: () => Get.back(), child: const Text('تم')), ], ), ); } }