From 330dfc6dba4d012682bbea785e1b9ba6f7a44b65 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sun, 26 Jul 2026 13:15:45 +0300 Subject: [PATCH] =?UTF-8?q?=D8=A5=D8=B5=D9=84=D8=A7=D8=AD=20=D8=A3=D8=AE?= =?UTF-8?q?=D8=B7=D8=A7=D8=A1=20=D8=B4=D8=A7=D8=B4=D8=A9=20=D8=A7=D9=84?= =?UTF-8?q?=D9=83=D8=A8=D8=A7=D8=AA=D9=86=20=D9=85=D9=86=20=D8=AA=D8=AD?= =?UTF-8?q?=D8=AF=D9=8A=D8=AB=20=D8=A7=D9=84=D8=B0=D9=83=D8=A7=D8=A1=20?= =?UTF-8?q?=D8=A7=D9=84=D8=A7=D8=B5=D8=B7=D9=86=D8=A7=D8=B9=D9=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/siro-admin/flutter_bootstrap.js | 2 +- sidebar_audit_report.json | 466 +++++++++++++++++ .../admin/ride_admin_controller.dart | 9 +- .../admin/wallet_admin_controller.dart | 12 +- .../drivers/driver_not_active_controller.dart | 11 +- .../captain/syrian_driver_not_active.dart | 9 +- .../views/admin/quality/blacklist_page.dart | 24 +- .../views/admin/rides/ride_lookup_page.dart | 70 ++- .../lib/views/widgets/unified_app_bar.dart | 483 ++++++++++++++++++ 9 files changed, 1025 insertions(+), 61 deletions(-) create mode 100644 sidebar_audit_report.json create mode 100644 siro_admin/lib/views/widgets/unified_app_bar.dart diff --git a/dashboard/siro-admin/flutter_bootstrap.js b/dashboard/siro-admin/flutter_bootstrap.js index 6d06146e..00f108a4 100644 --- a/dashboard/siro-admin/flutter_bootstrap.js +++ b/dashboard/siro-admin/flutter_bootstrap.js @@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"6c0baaebf70e0148f485f27d5616b3d3382da7 _flutter.loader.load({ serviceWorkerSettings: { - serviceWorkerVersion: "578408601" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */ + serviceWorkerVersion: "4149845318" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */ } }); diff --git a/sidebar_audit_report.json b/sidebar_audit_report.json new file mode 100644 index 00000000..6f1f3e3a --- /dev/null +++ b/sidebar_audit_report.json @@ -0,0 +1,466 @@ +{ + "pages": [ + { + "route": "/admin/home", + "file": "lib/views/admin/admin_home_page.dart", + "title": "Admin Home Page (Dashboard)", + "appBarPattern": "SliverAppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "charts|grid|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom AppBar with FlexibleSpaceBar; complex chart implementations; uses DashboardV2Widget" + }, + { + "route": "/admin/analytics/live", + "file": "lib/views/admin/analytics/live_analytics_page.dart", + "title": "Live Analytics Page", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "charts|map|tables", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Standard AppBar with TabBar; flutter_map integration; shared widget classes for charts" + }, + { + "route": "/admin/analytics/advanced", + "file": "lib/views/admin/static/advanced_analytics_page.dart", + "title": "Advanced Analytics Page", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "charts|lists|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; Column+SingleChildScrollView instead of CustomScrollView; inline _LegendDot" + }, + { + "route": "/admin/static", + "file": "lib/views/admin/static/static.dart", + "title": "System Statistics (StaticDash)", + "appBarPattern": "SliverAppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "charts|lists", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Complex SliverAppBar; extensive custom chart widgets; many inline helper classes" + }, + { + "route": "/admin/passenger", + "file": "lib/views/admin/passenger/passenger.dart", + "title": "Passenger Management", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; Column+Expanded+GetBuilder pattern; consistent cs usage" + }, + { + "route": "/admin/captain", + "file": "lib/views/admin/captain/captain.dart", + "title": "Captain List", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; search section separate; ListView.separated with cards" + }, + { + "route": "/admin/drivers/pending", + "file": "lib/views/admin/captain/syrian_driver_not_active.dart", + "title": "Pending Captain Requests", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Misleading filename; Custom Container header; RefreshIndicator; status chips" + }, + { + "route": "/driver-docs", + "file": "lib/views/admin/drivers/driver_documents_review_page.dart", + "title": "Driver Documents Review", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards|images", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Standard AppBar; pagination with loadMore; ExpansionTile for doc details with images" + }, + { + "route": "/admin/drivers/gifts", + "file": "lib/views/admin/drivers/driver_gift_check_page.dart", + "title": "Driver Gifts & Offers", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": true, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "form|log", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Custom Container header; hardcoded Color(0xFFF59E0B); terminal log with hardcoded green Color(0xFF4ADE80)" + }, + { + "route": "/admin/quality/blacklist", + "file": "lib/views/admin/quality/blacklist_page.dart", + "title": "Blacklist Page", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "tabs|lists", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header with TabBar; TabBar in Container not AppBar.bottom; Dialog for unblock" + }, + { + "route": "/admin/rides/dashboard", + "file": "lib/views/admin/rides/rides.dart", + "title": "Rides Dashboard", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "charts|grid|cards", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Custom Container header; SingleChildScrollView+Column; LineChart; GridView for metrics" + }, + { + "route": "/admin/rides/lookup", + "file": "lib/views/admin/rides/ride_lookup_page.dart", + "title": "Ride Lookup / Management", + "appBarPattern": "SliverAppBar", + "usesCsColors": false, + "hasHardcodedColors": true, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards|map", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "HARDCODED colors (bgColor, primaryColor, textPrimary); no Theme.of(context).colorScheme; complex SliverAppBar; custom ride cards; separate map screen" + }, + { + "route": "/admin/drivers/monitor", + "file": "lib/views/admin/drivers/monitor_ride.dart", + "title": "Live Ride Monitoring", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "map|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom header for search; two views (search + map); loading/error overlays on map; bottom info panel" + }, + { + "route": "/admin/drivers/tracker", + "file": "lib/views/admin/drivers/driver_tracker_screen.dart", + "title": "Captain Tracker (Siro Tracker)", + "appBarPattern": "Container-based (Positioned)", + "usesCsColors": true, + "hasHardcodedColors": true, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "map|dashboard", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "HARDCODED dark theme (Color(0xFF0A0E27)); Stack with Positioned overlays; custom dashboard panel; no standard Scaffold/AppBar" + }, + { + "route": "/transit/orgs", + "file": "lib/views/transit/org_list_page.dart", + "title": "Transit Organizations", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Standard AppBar; simple ListView with Cards; RefreshIndicator; clean minimal" + }, + { + "route": "/admin/financial/v2", + "file": "lib/views/admin/financial/financial_v2_page.dart", + "title": "Financial Report V2", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "cards|charts|lists", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Standard AppBar with refresh; SingleChildScrollView+Column; LinearProgressIndicator; ListView.builder shrinkWrap" + }, + { + "route": "/admin/wallet", + "file": "lib/views/admin/wallet/wallet.dart", + "title": "Financial Wallets", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; Pay All button; custom MyCircularProgressIndicator; consistent cs usage" + }, + { + "route": "/admin/packages", + "file": "lib/views/admin/packages.dart", + "title": "Packages & Subscriptions", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; Get.dialog for edit; platform icons; controller in same file" + }, + { + "route": "/invoice", + "file": "lib/views/invoice/invoice_list_page.dart", + "title": "Invoice List", + "appBarPattern": "Container-based (gradient)", + "usesCsColors": true, + "hasHardcodedColors": true, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards|images", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Gradient Container header (not AppBar); FAB; InteractiveViewer for images; hardcoded Colors.white in header" + }, + { + "route": "/kazan", + "file": "lib/views/admin/pricing/kazan_editor_page.dart", + "title": "Kazan Pricing Editor", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": true, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "form|grid", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Custom Container header; hardcoded car type colors; GoogleFonts; GridView for km prices; Slider for percentages" + }, + { + "route": "/promo", + "file": "lib/views/admin/promo/promo_management_page.dart", + "title": "Promo Code Management", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Standard AppBar; BottomSheet forms; PopupMenuButton; date pickers; GoogleFonts; custom widgets MyTextForm/MyElevatedButton" + }, + { + "route": "/marketing", + "file": "lib/views/admin/marketing/marketing_page.dart", + "title": "Marketing & AI Automation", + "appBarPattern": "AppBar (complex bottom)", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "tabs|charts|lists|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "AppBar with PreferredSize bottom (country selector + TabBar); 3 tabs; FlChart charts; inline Card widgets; AI prompt editor" + }, + { + "route": "/heatmap", + "file": "lib/views/admin/marketing/heatmap_page.dart", + "title": "Heatmap Page", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "map|filters", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Custom Container header; FlutterMap with CircleLayer; filter chips; Dio HTTP; legend overlay" + }, + { + "route": "/social-intelligence", + "file": "lib/views/admin/marketing/social_intelligence_screen.dart", + "title": "Social Intelligence", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|html", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; http package directly; HtmlWidget for HTML; ExpansionTile reports" + }, + { + "route": "/complaints", + "file": "lib/views/admin/complaints/complaint_list_page.dart", + "title": "Complaints Management", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Custom Container header; delayed complaints banner; ExpansionTile; BottomSheet with dropdown; GoogleFonts" + }, + { + "route": "/admin/server", + "file": "lib/views/admin/server/monitor_server_page.dart", + "title": "Server Monitor", + "appBarPattern": "SliverAppBar", + "usesCsColors": false, + "hasHardcodedColors": true, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "cards|charts|lists", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "HARDCODED dark theme (Color(0xFF0A0E27)); no Theme.of(context).colorScheme; SliverAppBar with gradient; LayoutBuilder responsive; all colors hardcoded" + }, + { + "route": "/admin/security/audit-logs", + "file": "lib/views/admin/security/audit_logs_page.dart", + "title": "Audit Logs", + "appBarPattern": "Container-based", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Custom Container header; GetBuilder with SecurityV2Controller; simple ListView; empty state with DB hint" + }, + { + "route": "/admin/employee", + "file": "lib/views/admin/employee/employee_page.dart", + "title": "Employee Management", + "appBarPattern": "SliverAppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards|images", + "hasLoadingState": false, + "hasEmptyState": true, + "issues": "SliverAppBar with FlexibleSpaceBar; CustomScrollView with Slivers; FAB; image viewers for IDs; form inline in same file" + }, + { + "route": "/admin/staff/add", + "file": "lib/views/admin/staff/add_staff_page.dart", + "title": "Add Staff Page", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "form", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Standard AppBar; form with helper methods; Dropdown for gender/country; role via constructor" + }, + { + "route": "/admin/staff/pending", + "file": "lib/views/admin/staff/pending_admins_page.dart", + "title": "Pending Admins", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": true, + "issues": "Standard AppBar with surfaceContainerHighest bg; StatefulWidget with setState; CRUD in widget; action buttons per item" + }, + { + "route": "/admin/error", + "file": "lib/views/admin/error/error/error_page.dart", + "title": "Error Logs", + "appBarPattern": "AppBar", + "usesCsColors": true, + "hasHardcodedColors": false, + "importsColorsDart": false, + "importsThemeDart": false, + "dataDisplay": "list|cards", + "hasLoadingState": true, + "hasEmptyState": false, + "issues": "Standard AppBar; search with phone filter; ExpansionTile for details; multiple _buildInfoBadge chips; StatefulWidget; ErrorLog model in same file" + } + ], + "summary": { + "totalPages": 31, + "appBarPatterns": { + "AppBar": 8, + "SliverAppBar": 4, + "Container-based (custom)": 19 + }, + "colorSystemUsage": { + "usesColorScheme": 27, + "hasHardcodedColors": 4 + }, + "imports": { + "importsColorsDart": 0, + "importsThemeDart": 0 + }, + "dataDisplayTypes": { + "list": 22, + "cards": 20, + "charts": 9, + "map": 4, + "tables": 3, + "forms": 5, + "images": 3, + "html": 1 + }, + "loadingStates": 29, + "emptyStates": 22 + } +} diff --git a/siro_admin/lib/controller/admin/ride_admin_controller.dart b/siro_admin/lib/controller/admin/ride_admin_controller.dart index ee1ffcdb..8d49f342 100644 --- a/siro_admin/lib/controller/admin/ride_admin_controller.dart +++ b/siro_admin/lib/controller/admin/ride_admin_controller.dart @@ -40,15 +40,14 @@ class RideAdminController extends GetxController { } Future getRidesDetails() async { - // isLoading = true; - // update(); + isLoading = true; + update(); var res = await CRUD().get(link: AppLink.getRidesDetails, payload: {}); var d = jsonDecode(res); ridesDetails = d['message']; - - // isLoading = false; - // update(); + isLoading = false; + update(); } @override diff --git a/siro_admin/lib/controller/admin/wallet_admin_controller.dart b/siro_admin/lib/controller/admin/wallet_admin_controller.dart index 64445822..781998b4 100644 --- a/siro_admin/lib/controller/admin/wallet_admin_controller.dart +++ b/siro_admin/lib/controller/admin/wallet_admin_controller.dart @@ -176,19 +176,21 @@ class WalletAdminController extends GetxController { return token; } - Future getWalletForEachDriverToPay() async { +Future getWalletForEachDriverToPay() async { isLoading = true; update(); var res = await CRUD() .postWallet(link: AppLink.getVisaForEachDriver, payload: {}); var d = (res); if (d != 'failure') { - driversWalletPoints = d['message']; + driversWalletPoints = d['message'] ?? []; + isLoading = false; + update(); + } else { + driversWalletPoints = []; isLoading = false; update(); } - driversWalletPoints = []; - isLoading = false; - update(); + } } } diff --git a/siro_admin/lib/controller/drivers/driver_not_active_controller.dart b/siro_admin/lib/controller/drivers/driver_not_active_controller.dart index 071b4391..1234f5be 100644 --- a/siro_admin/lib/controller/drivers/driver_not_active_controller.dart +++ b/siro_admin/lib/controller/drivers/driver_not_active_controller.dart @@ -5,22 +5,29 @@ import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class DriverController extends GetxController { + bool isLoading = false; List drivers = []; Map driverDetails = {}; // جلب السائقين pending getDriversPending() async { + isLoading = true; + update(); var res = await CRUD().post( - link: AppLink.getDriversPending, // رابط drivers_pending_list.php + link: AppLink.getDriversPending, payload: {}, ); if (res != 'failure') { drivers = (res)['message']; - update(['drivers']); // تحديث الـ UI + isLoading = false; + update(['drivers']); } else { mySnackbarError('Failed to load drivers'); + isLoading = false; + update(); } } + } // جلب تفاصيل سائق واحد getDriverDetails(String driverId) async { diff --git a/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart b/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart index 6c9c6867..a1849ea4 100644 --- a/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart +++ b/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart @@ -23,13 +23,16 @@ class DriversPendingPage extends StatelessWidget { body: Column( children: [ _buildAppBar(context, cs), - Expanded( +Expanded( child: GetBuilder( id: 'drivers', - builder: (c) { if (c.drivers.isEmpty) { + builder: (c) { + if (c.isLoading) { + return _buildLoadingState(cs); + } + if (c.drivers.isEmpty) { return _buildEmptyState(cs); } - return RefreshIndicator( onRefresh: () => controller.getDriversPending(), color: cs.primary, diff --git a/siro_admin/lib/views/admin/quality/blacklist_page.dart b/siro_admin/lib/views/admin/quality/blacklist_page.dart index 712c5ccf..52253c88 100644 --- a/siro_admin/lib/views/admin/quality/blacklist_page.dart +++ b/siro_admin/lib/views/admin/quality/blacklist_page.dart @@ -147,13 +147,16 @@ class BlacklistPage extends StatelessWidget { itemCount: controller.driversBlacklist.length, itemBuilder: (context, index) { var driver = controller.driversBlacklist[index]; + final String phone = (driver['phone'] ?? driver['phone_normalized'] ?? driver['mobile'] ?? driver['number'] ?? '').toString(); + final String reason = (driver['reason'] ?? driver['block_reason'] ?? driver['note'] ?? 'غير محدد').toString(); + final String date = (driver['created_at'] ?? driver['createdAt'] ?? driver['date'] ?? driver['blocked_at'] ?? '').toString(); return _buildBlacklistItem( - phone: driver['phone'], - reason: driver['reason'] ?? 'غير محدد', - date: driver['created_at'], + phone: phone, + reason: reason, + date: date, type: 'سائق', icon: Icons.drive_eta_rounded, - onUnblock: () => controller.unblockDriver(driver['phone'].toString()), + onUnblock: () => controller.unblockDriver(phone), cs: cs, ); }, @@ -170,14 +173,17 @@ class BlacklistPage extends StatelessWidget { itemCount: controller.passengersBlacklist.length, itemBuilder: (context, index) { var passenger = controller.passengersBlacklist[index]; + final String phone = (passenger['phone'] ?? passenger['phone_normalized'] ?? passenger['mobile'] ?? passenger['number'] ?? '').toString(); + final String reason = (passenger['reason'] ?? passenger['block_reason'] ?? passenger['note'] ?? 'غير محدد').toString(); + final String date = (passenger['created_at'] ?? passenger['createdAt'] ?? passenger['date'] ?? passenger['blocked_at'] ?? '').toString(); + final String phoneForUnblock = (passenger['phone_normalized'] ?? passenger['phone'] ?? passenger['mobile'] ?? passenger['number'] ?? '').toString(); return _buildBlacklistItem( - phone: passenger['phone'] ?? passenger['phone_normalized'], - reason: passenger['reason'] ?? 'غير محدد', - date: passenger['created_at'], + phone: phone, + reason: reason, + date: date, type: 'راكب', icon: Icons.person_rounded, - onUnblock: () => controller.unblockPassenger( - passenger['phone_normalized'].toString()), + onUnblock: () => controller.unblockPassenger(phoneForUnblock), cs: cs, ); }, diff --git a/siro_admin/lib/views/admin/rides/ride_lookup_page.dart b/siro_admin/lib/views/admin/rides/ride_lookup_page.dart index 81b13dad..75107111 100644 --- a/siro_admin/lib/views/admin/rides/ride_lookup_page.dart +++ b/siro_admin/lib/views/admin/rides/ride_lookup_page.dart @@ -204,11 +204,6 @@ class _RidesDashboardScreenState extends State late final RidesListController controller; late TabController _tabController; - // 🎨 الألوان العصرية - final Color bgColor = const Color(0xFFF4F7FE); - final Color primaryColor = const Color(0xFF4318FF); - final Color textPrimary = const Color(0xFF2B3674); - @override void initState() { super.initState(); @@ -232,10 +227,11 @@ class _RidesDashboardScreenState extends State @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: bgColor, + backgroundColor: cs.surface, body: SafeArea( - top: false, // نسمح للـ AppBar بالتمدد لأعلى الشاشة + top: false, child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ @@ -243,16 +239,16 @@ class _RidesDashboardScreenState extends State SliverAppBar( pinned: true, floating: false, - expandedHeight: 310.0, // ارتفاع الجزء العلوي بالكامل - backgroundColor: primaryColor, + expandedHeight: 310.0, + backgroundColor: cs.primary, elevation: 4, - shadowColor: primaryColor.withOpacity(0.4), - iconTheme: const IconThemeData(color: Colors.white), + shadowColor: cs.primary.withValues(alpha: 0.4), + iconTheme: IconThemeData(color: cs.onPrimary), centerTitle: true, - title: const Text( + title: Text( 'إدارة الرحلات', style: TextStyle( - color: Colors.white, + color: cs.onPrimary, fontWeight: FontWeight.bold, fontSize: 20, ), @@ -263,16 +259,16 @@ class _RidesDashboardScreenState extends State const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: cs.onPrimary.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), alignment: Alignment.center, - child: const Text( + child: Text( 'اليوم', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: Colors.white, + color: cs.onPrimary, ), ), ), @@ -281,7 +277,10 @@ class _RidesDashboardScreenState extends State background: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [primaryColor, primaryColor.withOpacity(0.8)], + colors: [ + cs.primary, + cs.primary.withValues(alpha: 0.8), + ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), @@ -290,12 +289,12 @@ class _RidesDashboardScreenState extends State mainAxisAlignment: MainAxisAlignment.end, children: [ // الإحصائيات تختفي عند التمرير لأعلى - _buildStatisticsSection(), + _buildStatisticsSection(cs), const SizedBox(height: 12), // شريط البحث يختفي عند التمرير لأعلى Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: _buildSearchBar(), + child: _buildSearchBar(cs), ), const SizedBox(height: 60), // مساحة للـ TabBar بالأسفل ], @@ -306,18 +305,17 @@ class _RidesDashboardScreenState extends State bottom: PreferredSize( preferredSize: const Size.fromHeight(54), child: Container( - decoration: const BoxDecoration( - color: - Color(0xFFF4F7FE), // لون خلفية التطبيق ليظهر بشكل مدمج + decoration: BoxDecoration( + color: cs.surface, borderRadius: - BorderRadius.vertical(top: Radius.circular(24)), + const BorderRadius.vertical(top: Radius.circular(24)), ), child: TabBar( controller: _tabController, isScrollable: true, - labelColor: primaryColor, - unselectedLabelColor: Colors.grey, - indicatorColor: primaryColor, + labelColor: cs.primary, + unselectedLabelColor: cs.onSurfaceVariant, + indicatorColor: cs.primary, indicatorWeight: 3, labelStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14), @@ -359,13 +357,13 @@ class _RidesDashboardScreenState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.inbox_rounded, - size: 80, color: Colors.grey[400]), + size: 80, color: cs.onSurfaceVariant), const SizedBox(height: 16), Text( 'لا توجد رحلات في هذا القسم', style: TextStyle( fontSize: 16, - color: Colors.grey[600], + color: cs.onSurfaceVariant, fontWeight: FontWeight.w600, ), ), @@ -380,7 +378,7 @@ class _RidesDashboardScreenState extends State (context, index) { final ride = controller.displayedRides[index]; return _buildRideCardCompact( - ride, controller.isSuperAdmin); + ride, controller.isSuperAdmin, cs); }, childCount: controller.displayedRides.length, ), @@ -395,7 +393,7 @@ class _RidesDashboardScreenState extends State // --- Components --- - Widget _buildStatisticsSection() { + Widget _buildStatisticsSection(ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -446,17 +444,17 @@ class _RidesDashboardScreenState extends State } Widget _buildStatCard( - String label, String value, IconData icon, Color iconColor) { + String label, String value, IconData icon, Color iconColor, ColorScheme cs) { return Container( width: 105, margin: const EdgeInsets.only(left: 10), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: cs.shadow.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 4), ) @@ -470,7 +468,7 @@ class _RidesDashboardScreenState extends State Text( value, style: TextStyle( - fontSize: 18, fontWeight: FontWeight.bold, color: textPrimary), + fontSize: 18, fontWeight: FontWeight.bold, color: cs.onSurface), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -479,7 +477,7 @@ class _RidesDashboardScreenState extends State label, style: TextStyle( fontSize: 11, - color: Colors.grey[600], + color: cs.onSurfaceVariant, fontWeight: FontWeight.bold), ), ], @@ -487,7 +485,7 @@ class _RidesDashboardScreenState extends State ); } - Widget _buildSearchBar() { + Widget _buildSearchBar(ColorScheme cs) { return Container( decoration: BoxDecoration( color: Colors.white, diff --git a/siro_admin/lib/views/widgets/unified_app_bar.dart b/siro_admin/lib/views/widgets/unified_app_bar.dart new file mode 100644 index 00000000..d89c8f6c --- /dev/null +++ b/siro_admin/lib/views/widgets/unified_app_bar.dart @@ -0,0 +1,483 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class UnifiedAppBar extends StatelessWidget implements PreferredSizeWidget { + final String title; + final Widget? leading; + final List? actions; + final Color? backgroundColor; + final Color? foregroundColor; + final double elevation; + final bool centerTitle; + final PreferredSizeWidget? bottom; + final double toolbarHeight; + + const UnifiedAppBar({ + super.key, + required this.title, + this.leading, + this.actions, + this.backgroundColor, + this.foregroundColor, + this.elevation = 0, + this.centerTitle = false, + this.bottom, + this.toolbarHeight = kToolbarHeight + 34, + }); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final bgColor = backgroundColor ?? cs.surface; + + return Container( + height: toolbarHeight, + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: bgColor, + border: Border(bottom: BorderSide(color: cs.outline, width: 0.5)), + ), + child: SafeArea( + bottom: false, + child: Row( + children: [ + leading ?? + 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), + Expanded( + child: Text( + title, + style: TextStyle( + color: foregroundColor ?? cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (actions != null) ...actions!, + ], + ), + ), + ); + } + + @override + Size get preferredSize => Size.fromHeight(toolbarHeight); +} + +class UnifiedAppBarWithTabs extends StatelessWidget implements PreferredSizeWidget { + final String title; + final List tabs; + final TabController? tabController; + final Widget? leading; + final List? actions; + final Color? backgroundColor; + final Color? foregroundColor; + final double elevation; + final EdgeInsetsGeometry? tabPadding; + + const UnifiedAppBarWithTabs({ + super.key, + required this.title, + required this.tabs, + this.tabController, + this.leading, + this.actions, + this.backgroundColor, + this.foregroundColor, + this.elevation = 0, + this.tabPadding, + }); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final bgColor = backgroundColor ?? cs.surface; + + return Container( + height: kToolbarHeight + 34 + 48, + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: bgColor, + border: Border(bottom: BorderSide(color: cs.outline, width: 0.5)), + ), + child: SafeArea( + bottom: false, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + leading ?? + 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), + Expanded( + child: Text( + title, + style: TextStyle( + color: foregroundColor ?? cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (actions != null) ...actions!, + ], + ), + const SizedBox(height: 8), + Container( + padding: tabPadding ?? const EdgeInsets.all(4), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: cs.outline), + ), + child: TabBar( + controller: tabController, + isScrollable: true, + indicator: BoxDecoration( + color: cs.primary.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: cs.primary.withValues(alpha: 0.3)), + ), + indicatorSize: TabBarIndicatorSize.tab, + dividerColor: Colors.transparent, + labelColor: cs.primary, + unselectedLabelColor: cs.onSurfaceVariant, + labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13), + unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w500, fontSize: 13), + tabs: tabs, + ), + ), + ], + ), + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight + 34 + 48); +} + +class UnifiedScaffold extends StatelessWidget { + final String title; + final Widget body; + final List? actions; + final Widget? leading; + final Color? backgroundColor; + final bool extendBody; + final FloatingActionButton? floatingActionButton; + final FloatingActionButtonLocation? floatingActionButtonLocation; + final PreferredSizeWidget? bottomNavigationBar; + + const UnifiedScaffold({ + super.key, + required this.title, + required this.body, + this.actions, + this.leading, + this.backgroundColor, + this.extendBody = false, + this.floatingActionButton, + this.floatingActionButtonLocation, + this.bottomNavigationBar, + }); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: backgroundColor ?? cs.surface, + extendBody: extendBody, + floatingActionButton: floatingActionButton, + floatingActionButtonLocation: floatingActionButtonLocation, + bottomNavigationBar: bottomNavigationBar, + appBar: UnifiedAppBar( + title: title, + actions: actions, + leading: leading, + backgroundColor: backgroundColor, + ), + body: body, + ); + } +} + +class UnifiedLoadingState extends StatelessWidget { + final String? message; + final Color? color; + + const UnifiedLoadingState({super.key, this.message, this.color}); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: color ?? cs.primary, + strokeWidth: 2, + backgroundColor: (color ?? cs.primary).withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text( + message ?? 'جاري التحميل...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), + ), + ], + ), + ); + } +} + +class UnifiedEmptyState extends StatelessWidget { + final IconData icon; + final String title; + final String? subtitle; + final Widget? action; + + const UnifiedEmptyState({ + super.key, + required this.icon, + required this.title, + this.subtitle, + this.action, + }); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(icon, size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text( + title, + style: TextStyle(color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600), + textAlign: TextAlign.center, + ), + if (subtitle != null) ...[ + const SizedBox(height: 8), + Text( + subtitle!, + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), + textAlign: TextAlign.center, + ), + ], + if (action != null) ...[ + const SizedBox(height: 20), + action!, + ], + ], + ), + ), + ); + } +} + +class UnifiedStatCard extends StatelessWidget { + final String label; + final String value; + final IconData icon; + final Color color; + final VoidCallback? onTap; + final String? subtitle; + + const UnifiedStatCard({ + super.key, + required this.label, + required this.value, + required this.icon, + required this.color, + this.onTap, + this.subtitle, + }); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(16), + splashColor: color.withValues(alpha: 0.1), + highlightColor: color.withValues(alpha: 0.05), + child: Container( + width: 155, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.all(7), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(9), + ), + child: Icon(icon, color: color, size: 16), + ), + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color.withValues(alpha: 0.6), + ), + ), + ], + ), + const Spacer(), + Text( + value, + style: TextStyle( + color: cs.onSurface, + fontSize: 20, + fontWeight: FontWeight.w700, + height: 1.1, + ), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 3), + Text( + label, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 10, + fontWeight: FontWeight.w500, + ), + overflow: TextOverflow.ellipsis, + ), + if (subtitle != null) ...[ + const SizedBox(height: 2), + Text( + subtitle!, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 9, + fontWeight: FontWeight.w400, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ], + ), + ), + ), + ); + } +} + +class UnifiedSectionHeader extends StatelessWidget { + final String title; + final IconData? icon; + final Color? iconColor; + final Widget? trailing; + + const UnifiedSectionHeader({ + super.key, + required this.title, + this.icon, + this.iconColor, + this.trailing, + }); + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Padding( + padding: const EdgeInsets.fromLTRB(20, 8, 20, 10), + child: Row( + children: [ + Container( + width: 3, + height: 14, + decoration: BoxDecoration( + color: iconColor ?? cs.primary, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + if (icon != null) ...[ + Icon(icon!, size: 16, color: iconColor ?? cs.primary), + const SizedBox(width: 6), + ], + Text( + title, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: 1.2, + ), + ), + if (trailing != null) ...[ + const Spacer(), + trailing!, + ], + ], + ), + ); + } +} \ No newline at end of file