diff --git a/android_bot/app/src/main/java/com/siro/android_bot/network/WorkerClient.kt b/android_bot/app/src/main/java/com/siro/android_bot/network/WorkerClient.kt index 7c060ef8..35a83aff 100644 --- a/android_bot/app/src/main/java/com/siro/android_bot/network/WorkerClient.kt +++ b/android_bot/app/src/main/java/com/siro/android_bot/network/WorkerClient.kt @@ -29,7 +29,10 @@ class WorkerClient(private val context: Context) { // حتى يكون تبديله عند نسخ البوت لعلامة أخرى سطراً واحداً في gradle. // للتجريب المحلي: اضبط BACKEND_HOST على "http://10.0.2.2:8000" مع تقديم // نفس المسار /backend/bot/ من جذر السيرفر المحلي. - private val BASE_URL = "${BuildConfig.BACKEND_HOST}/backend/bot/standalone_worker.php" + // worker.php هو المسار الإنتاجي (Redis + MySQL). لا تُعِده إلى + // standalone_worker.php: تلك نسخة اختبار محلي تخزّن في ملفات JSON + // ولا تصل نتائجها إلى جدول scraped_competitor_prices ولا إلى محرّك التسعير. + private val BASE_URL = "${BuildConfig.BACKEND_HOST}/backend/bot/worker.php" private fun generateSignature(deviceId: String, ts: Long): String { val message = "$deviceId$ts" diff --git a/backend/Admin/dashbord.php b/backend/Admin/dashbord.php index ea56b6ae..9495a27e 100644 --- a/backend/Admin/dashbord.php +++ b/backend/Admin/dashbord.php @@ -33,15 +33,20 @@ SELECT -- كانت تُنتج متوسط مدة سالباً. (SELECT TIME_FORMAT(SEC_TO_TIME(AVG(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))), '%Hh %im') FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL AND TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish) > 0) AS driver_avg_duration, (SELECT MAX(SEC_TO_TIME(TIMESTAMPDIFF(SECOND, rideTimeStart, rideTimeFinish))) FROM ride WHERE rideTimeStart IS NOT NULL AND rideTimeFinish IS NOT NULL) AS longest_duration, - (SELECT ROUND(SUM(distance),2) FROM ride) AS total_distance, - (SELECT ROUND(AVG(distance),2) FROM ride) AS average_distance, - (SELECT ROUND(MAX(distance),2) FROM ride) AS longest_distance, + -- المسافات تُحسب على الرحلات المكتملة فقط. حسابها على كل الصفوف كان + -- يُدخل الرحلات الملغاة و timeout (مسافتها 0 أو غير مضبوطة) في المتوسط + -- فيهبط average_distance بشكل مضلّل. + (SELECT ROUND(SUM(distance),2) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS total_distance, + (SELECT ROUND(AVG(distance),2) FROM ride WHERE LOWER(status) IN ('finished','completed') AND distance > 0) AS average_distance, + (SELECT ROUND(MAX(distance),2) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS longest_distance, -- أرباح السائق والشركة -- ملاحظة: خط الرحلات الحالي يكتب 'completed' بينما القديم يكتب 'Finished'، -- والاكتفاء بالقديم كان يُرجع NULL للأرباح وصفراً للرحلات المكتملة/الملغاة. (SELECT SUM(price_for_driver) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS total_driver_earnings, - (SELECT ROUND(AVG(price_for_passenger),2) FROM ride) AS avg_passenger_price, + -- متوسط سعر الراكب على الرحلات المكتملة فقط — الرحلات الملغاة سعرها 0 + -- وكانت تسحب المتوسط إلى أسفل. + (SELECT ROUND(AVG(price_for_passenger),2) FROM ride WHERE LOWER(status) IN ('finished','completed') AND price_for_passenger > 0) AS avg_passenger_price, -- توزيع الرحلات حسب الوقت (SELECT COUNT(*) FROM ride WHERE HOUR(created_at) BETWEEN 6 AND 11) AS morning_ride_count, @@ -59,7 +64,9 @@ SELECT (SELECT COUNT(*) FROM ride WHERE LOWER(status) LIKE 'cancel%' OR LOWER(status) IN ('timeout','refused')) AS cancelled_rides, -- عدد السائقين الفريدين - (SELECT COUNT(*) FROM (SELECT driver_id FROM ride GROUP BY driver_id) AS sub) AS num_Driver, + -- cancel_ride_by_driver.php يضبط driver_id = 0 عند الإلغاء، وكان هذا + -- الصفر يُحتسب سائقاً وهمياً إضافياً في العدد. + (SELECT COUNT(DISTINCT driver_id) FROM ride WHERE driver_id > 0) AS num_Driver, -- التحويلات البنكية 0 AS transfer_from_count diff --git a/backend/bot/cron_ai_engine.php b/backend/bot/cron_ai_engine.php index 8c23e331..ba8aa1f4 100644 --- a/backend/bot/cron_ai_engine.php +++ b/backend/bot/cron_ai_engine.php @@ -19,6 +19,14 @@ try { die("Connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. +// كل مخرجات هذا المحرّك تُكتب في Redis (مقارنة السائقين، ملخّص ذروة +// المنافسين، المناطق الساخنة)، فلا معنى لمتابعة التشغيل بدونه. +if (!$redis) { + error_log('[AI Engine] ABORT: Redis unavailable — no cache target to write to.'); + die("Redis unavailable — aborting AI engine run.\n"); +} + echo "Starting Siro AI Engine v2 (Powered by Pricing Engine)...\n"; // ========================================== diff --git a/backend/bot/cron_auto_marketing_pusher.php b/backend/bot/cron_auto_marketing_pusher.php index 01bb568b..a512ca95 100644 --- a/backend/bot/cron_auto_marketing_pusher.php +++ b/backend/bot/cron_auto_marketing_pusher.php @@ -26,6 +26,14 @@ try { die("Database connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. +// مفتاح مكافحة التكرار (TTL يوم كامل) يعيش في Redis؛ بدونه يُعاد إرسال +// نفس الحملة التسويقية كل ساعة. +if (!$redis) { + error_log('[AutoMarketingPusher] ABORT: Redis unavailable — anti-spam guard would fail open.'); + die("Redis unavailable — aborting to avoid duplicate campaign pushes.\n"); +} + echo "Starting Auto Marketing Pusher Engine...\n"; // 1. استخراج الركاب الخاملين في آخر ساعتين (الذين لم يقموا بأي طلب اليوم) diff --git a/backend/bot/cron_generate_heatmap_cache.php b/backend/bot/cron_generate_heatmap_cache.php index ee051115..fc6ca4c2 100644 --- a/backend/bot/cron_generate_heatmap_cache.php +++ b/backend/bot/cron_generate_heatmap_cache.php @@ -14,6 +14,13 @@ try { die("Connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. +// الهدف الوحيد لهذا السكربت هو الكتابة إلى مفتاح Redis للكاش. +if (!$redis) { + error_log('[HeatmapCache] ABORT: Redis unavailable — nothing to cache into.'); + die("Redis unavailable — aborting heatmap cache generation.\n"); +} + echo "Starting Heatmap Cache Generation (Redis)...\n"; // مربعات المدن الكبرى لتمثيل الدول (لتجنب حساب المضلعات المعقدة) diff --git a/backend/bot/cron_kazan_adjuster.php b/backend/bot/cron_kazan_adjuster.php index b8c74ef7..51fd4e50 100644 --- a/backend/bot/cron_kazan_adjuster.php +++ b/backend/bot/cron_kazan_adjuster.php @@ -18,6 +18,14 @@ try { die("Connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() تُعيد المتغير العام $redis فقط، وbootstrap.php يتركه null +// عند فشل الاتصال — فهي لا ترمي استثناءً والـ try/catch أعلاه لا يلتقط شيئاً. +// بدون هذا الفحص كان السكربت ينهار فوراً بـ "call to a member function get() on null". +if (!$redis) { + error_log('[cron_kazan_adjuster] ABORT: Redis unavailable — Kazan discounts left untouched.'); + die("Redis unavailable — aborting without touching Kazan discounts.\n"); +} + $countries = ['SY', 'JO', 'EG', 'IQ']; foreach ($countries as $country) { diff --git a/backend/bot/cron_passenger_reengagement.php b/backend/bot/cron_passenger_reengagement.php index 6d38698d..02bbb384 100644 --- a/backend/bot/cron_passenger_reengagement.php +++ b/backend/bot/cron_passenger_reengagement.php @@ -28,6 +28,14 @@ try { die("[ReEngagement] Connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. +// مفتاح مكافحة التكرار يعيش في Redis؛ بدونه يفشل الفحص "مفتوحاً" +// فيتلقّى كل راكب نفس رسالة الاستعادة في كل تشغيل. +if (!$redis) { + error_log('[ReEngagement] ABORT: Redis unavailable — anti-spam guard would fail open.'); + die("[ReEngagement] Redis unavailable — aborting to avoid duplicate pushes.\n"); +} + echo "[ReEngagement] Starting Passenger Re-engagement Engine...\n"; $REDIS_TTL = 72 * 3600; // anti-spam: 72 ساعة diff --git a/backend/bot/cron_predictive_demand.php b/backend/bot/cron_predictive_demand.php index cddbcab3..7bc8ec4c 100644 --- a/backend/bot/cron_predictive_demand.php +++ b/backend/bot/cron_predictive_demand.php @@ -34,6 +34,14 @@ try { die("[PredictiveDemand] Connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. +// الإيقاف هنا مقصود: مفتاح مكافحة التكرار يعيش في Redis، وبدونه سيُعاد +// إرسال نفس الإشعارات للركاب في كل تشغيل. +if (!$redis) { + error_log('[PredictiveDemand] ABORT: Redis unavailable — anti-spam guard would fail open.'); + die("[PredictiveDemand] Redis unavailable — aborting to avoid duplicate pushes.\n"); +} + echo "[PredictiveDemand] Starting...\n"; $GRID_SIZE = 0.01; // حجم الخلية (~1.1km) diff --git a/backend/bot/cron_seasonal_pricing.php b/backend/bot/cron_seasonal_pricing.php index c8ea63ac..a70591f4 100644 --- a/backend/bot/cron_seasonal_pricing.php +++ b/backend/bot/cron_seasonal_pricing.php @@ -15,6 +15,13 @@ try { die("Connection failed: " . $e->getMessage() . "\n"); } +// getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. +// المُعاملات الموسمية تُنشر عبر Redis وحده ليقرأها محرّك التسعير. +if (!$redis) { + error_log('[SeasonalPricing] ABORT: Redis unavailable — seasonal multipliers not published.'); + die("Redis unavailable — aborting seasonal pricing run.\n"); +} + $countries = ['SY' => 'Asia/Damascus', 'JO' => 'Asia/Amman', 'EG' => 'Africa/Cairo', 'IQ' => 'Asia/Baghdad']; // Simulated Calendar of Events diff --git a/backend/bot/worker.php b/backend/bot/worker.php index a2b2b171..01df4785 100644 --- a/backend/bot/worker.php +++ b/backend/bot/worker.php @@ -23,7 +23,15 @@ if (!$SECRET_KEY) { echo json_encode(['status' => 'failure', 'message' => 'Server configuration error: BOT_SECRET_KEY not set in environment']); exit; } -$ALLOWED_DEVICES = ['SHAM_CASH_BOT_01', 'PRICE_SCRAPER_BOT_01', '77a9528112ef7c2a']; +// أجهزة البوت المسموح بها — تُضبط عبر BOT_ALLOWED_DEVICES (مفصولة بفواصل) +// حتى لا يتطلب ضم جهاز جديد تعديل الكود ونشره. +$ALLOWED_DEVICES = array_values(array_filter(array_map( + 'trim', + explode(',', (string)(getenv('BOT_ALLOWED_DEVICES') ?: '')) +))); +if (!$ALLOWED_DEVICES) { + $ALLOWED_DEVICES = ['SHAM_CASH_BOT_01', 'PRICE_SCRAPER_BOT_01', '77a9528112ef7c2a']; +} $method = $_SERVER['REQUEST_METHOD']; @@ -43,9 +51,12 @@ if ($method === 'GET') { // --------------------------------------------------------- // GET: Fetch Pending Task for the Bot // --------------------------------------------------------- - $device_id = filterRequest('device_id'); - $ts = filterRequest('ts'); - $sig = filterRequest('sig'); + // ملاحظة: filterRequest() تقرأ من POST أو JSON body فقط ولا تلمس $_GET + // (backend/core/helpers.php) — والبوت يرسل بارامترات التوقيع في الـ query + // string، لذا تُقرأ هنا من $_GET مباشرة. + $device_id = isset($_GET['device_id']) ? trim((string)$_GET['device_id']) : null; + $ts = isset($_GET['ts']) ? (int)$_GET['ts'] : null; + $sig = isset($_GET['sig']) ? trim((string)$_GET['sig']) : null; if (!$device_id || !$ts || !$sig) { jsonError("Missing security parameters"); @@ -64,6 +75,14 @@ if ($method === 'GET') { // Check Redis Queue for tasks // Queue Name: queue:bot:tasks (Using Main Redis) + // bootstrap.php يترك $redis = null إذا فشل الاتصال — بدون هذا الفحص + // كان الطلب ينهار بخطأ 500 غامض بدل رسالة واضحة. + if (!$redis) { + http_response_code(503); + echo json_encode(['status' => 'failure', 'message' => 'Task queue unavailable']); + exit; + } + $taskJson = $redis->rpop('queue:bot:tasks'); if ($taskJson) { @@ -144,18 +163,28 @@ if ($method === 'GET') { } // ------------------------------------------------------ + // العملة تتبع الدولة بدل تثبيتها على JOD — البوت يعمل على أكثر من سوق + $currencyByCountry = ['JO' => 'JOD', 'SY' => 'SYP', 'EG' => 'EGP']; + $currency = $currencyByCountry[strtoupper($country_code)] ?? 'JOD'; + // 1. Save to MySQL - $stmt = $con->prepare(" - INSERT INTO scraped_competitor_prices - (task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, distance_km, duration_min, currency, country_code) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - "); - $start_loc = "Lat: $start_lat, Lng: $start_lng"; - $end_loc = "Lat: $end_lat, Lng: $end_lng"; - $stmt->execute([$task_id, $app_name, $app_name, $start_loc, $end_loc, (string)$start_lat, (string)$start_lng, (string)$end_lat, (string)$end_lng, $price, $pricePerKm, $distance_km, $duration_min, 'JOD', $country_code]); + try { + $stmt = $con->prepare(" + INSERT INTO scraped_competitor_prices + (task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, distance_km, duration_min, currency, country_code) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "); + $start_loc = "Lat: $start_lat, Lng: $start_lng"; + $end_loc = "Lat: $end_lat, Lng: $end_lng"; + $stmt->execute([$task_id, $app_name, $app_name, $start_loc, $end_loc, (string)$start_lat, (string)$start_lng, (string)$end_lat, (string)$end_lng, $price, $pricePerKm, $distance_km, $duration_min, $currency, $country_code]); + } catch (PDOException $e) { + // لا نُسقط الطلب: نسجّل الخطأ ونكمل إلى Redis حتى لا تضيع العيّنة + // من محرّك التسعير بسبب عطل في الكتابة إلى MySQL. + error_log("[Bot Worker] MySQL insert failed for task $task_id: " . $e->getMessage()); + } // 2. Save to Redis (Calculate Price Per KM) - if ($distance_km > 0 && $price > 0) { + if ($redis && $distance_km > 0 && $price > 0) { // Store in Redis (Main) to be used by Pricing Engine // Store recent 50 prices for the app $redis->lpush("competitor:price_history:$app_name", $pricePerKm); diff --git a/backend/migrations/2026_08_02_scraped_competitor_prices_add_distance.sql b/backend/migrations/2026_08_02_scraped_competitor_prices_add_distance.sql new file mode 100644 index 00000000..74be9d3b --- /dev/null +++ b/backend/migrations/2026_08_02_scraped_competitor_prices_add_distance.sql @@ -0,0 +1,19 @@ +-- ============================================================ +-- scraped_competitor_prices: add distance_km / duration_min +-- ============================================================ +-- backend/bot/worker.php computes both values via OSRM +-- (core/osrm_routing.php) and has always listed them in its +-- INSERT, but the columns were never created — so every +-- production bot submission failed with a PDOException. +-- +-- Consumers currently derive distance as +-- (price_amount / price_per_km) +-- e.g. Admin/marketing/what_if_simulator.php and +-- Admin/marketing/trigger_campaign.php +-- Those keep working unchanged; the new columns are additive +-- and nullable, so historical rows stay valid. +-- ============================================================ + +ALTER TABLE `scraped_competitor_prices` + ADD COLUMN `distance_km` DECIMAL(8,3) DEFAULT NULL AFTER `price_per_km`, + ADD COLUMN `duration_min` DECIMAL(8,2) DEFAULT NULL AFTER `distance_km`; diff --git a/backend/serviceapp/getRidesStatic.php b/backend/serviceapp/getRidesStatic.php index 80984efe..ffa3e32b 100644 --- a/backend/serviceapp/getRidesStatic.php +++ b/backend/serviceapp/getRidesStatic.php @@ -28,20 +28,20 @@ WITH RECURSIVE date_series AS ( FROM date_series WHERE date < :end_date ) -SELECT - date_series.date AS day, - COALESCE(SUM(ride.status = 'Finished'), 0) AS totalRides, - - (SELECT COUNT(*) FROM ride - WHERE ride.created_at >= :start_date_total - AND ride.created_at <= :end_date_total - AND ride.status = 'Finished') AS totalMonthly +SELECT + date_series.date AS day, + COALESCE(SUM(LOWER(ride.status) IN ('finished','completed')), 0) AS totalRides, -FROM + (SELECT COUNT(*) FROM ride + WHERE ride.created_at >= :start_date_total + AND ride.created_at <= :end_date_total + AND LOWER(ride.status) IN ('finished','completed')) AS totalMonthly + +FROM date_series -LEFT JOIN - ride ON DATE(ride.created_at) = date_series.date - AND ride.status = 'Finished' +LEFT JOIN + ride ON DATE(ride.created_at) = date_series.date + AND LOWER(ride.status) IN ('finished','completed') WHERE date_series.date >= :start_date_where AND date_series.date <= :end_date_where diff --git a/docker/.env.example b/docker/.env.example index 3260f0c5..5d7e7e67 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -60,6 +60,15 @@ REDIS_MAIN_HOST=redis REDIS_MAIN_PORT=6379 REDIS_MAIN_PASSWORD= +# --- 3.5 Android Scraper Bot --- +# backend/bot/worker.php يرفض كل طلب بخطأ 500 إذا كان BOT_SECRET_KEY فارغاً. +# يجب أن تطابق القيمة BOT_SECRET_KEY في android_bot/app/build.gradle.kts +# (تُبنى داخل الـ APK عبر BuildConfig). +BOT_SECRET_KEY= +# معرّفات الأجهزة المسموح لها (ANDROID_ID) مفصولة بفواصل. اتركها فارغة +# لاستخدام القائمة الافتراضية المدمجة في worker.php. +BOT_ALLOWED_DEVICES= + # --- 4. Internal Sockets (Docker Network Names) --- LOCATION_SERVER_URL=http://socket_driver:2021 RIDE_SOCKET_URL=http://socket_passenger:3031 diff --git a/docker/crontab.production b/docker/crontab.production new file mode 100644 index 00000000..51f50b2a --- /dev/null +++ b/docker/crontab.production @@ -0,0 +1,92 @@ +# ============================================================ +# جدول المهام المجدولة — سيرو الأردن (بعد الانتقال إلى دوكر) +# ============================================================ +# المشكلة التي يعالجها هذا الملف: +# +# الجدول القديم كان يشغّل كل السكربتات على المضيف مباشرة: +# /usr/bin/php8.4 /home/intaleqapp-jordan-siro/htdocs/.../backend/... +# +# بعد الانتقال إلى دوكر لم تعد هذه السكربتات قادرة على العمل: +# +# • Redis لا يَنشر أي بورت إلى المضيف (انظر خدمة redis في +# docker-compose.yml — لا يوجد فيها ports:). أي أن Redis يُرى فقط +# من داخل شبكة دوكر. كل سكربت على المضيف يحصل على $redis = null. +# +# • MySQL منشور على 127.0.0.1:33061 وليس 3306، فأي .env على المضيف +# ما زال يشير إلى 3306 يفشل في الاتصال. +# +# • bootstrap.php يستنتج مسار .env من __DIR__ عبر المطابقة مع +# ^/home/[^/]+ — فسكربتات المضيف تقرأ /home/intaleqapp-jordan-siro/.env +# (ملف ما قبل دوكر)، بينما الحاويات تقرأ متغيّراتها من docker/.env. +# نتيجةً لذلك كان الطرفان يعملان بإعدادات مختلفة تماماً. +# +# الحل: تشغيل كل شيء داخل حاوية php، حيث أسماء الشبكة (mysql / redis) +# والمتغيّرات صحيحة. هذا نفس الدرس المستفاد من عطل السوكيتات سابقاً. +# +# ------------------------------------------------------------ +# التركيب: +# crontab -e ثم الصق المحتوى أدناه +# +# ملاحظة: المستخدم الذي يملك الـ crontab يجب أن يكون عضواً في مجموعة +# docker، وإلا فشل كل سطر بخطأ صلاحيات. +# ------------------------------------------------------------ + +SIRO=/home/intaleqapp-jordan-siro/htdocs/jordan-siro.intaleqapp.com/docker +DEX=/usr/bin/docker compose -f $SIRO/docker-compose.yml exec -T php php + +# ============================================================ +# مواصلاتي (Transit) +# ============================================================ +*/1 * * * * $DEX /var/www/backend/transit/cron_approaching_alerts.php >> /var/log/transit_alerts.log 2>&1 +0 3 * * * $DEX /var/www/backend/transit/cron_sync_members.php >> /var/log/transit_sync.log 2>&1 +30 3 * * * $DEX /var/www/backend/transit/cron_cleanup.php >> /var/log/transit_cleanup.log 2>&1 + +# ============================================================ +# محرّك التسعير والبوتات +# ============================================================ +# تغذية طابور مهام البوت في Redis. +# كان الجدول القديم يشغّل generate_10_tasks_cli.php وهو يكتب إلى +# tasks.json الذي يقرأه standalone_worker.php (نسخة اختبار محلي بلا +# قاعدة بيانات). النتائج لم تكن تصل إلى scraped_competitor_prices إطلاقاً. +*/15 * * * * $DEX /var/www/backend/bot/push_10_tasks.php >> /var/log/bot_tasks.log 2>&1 + +*/5 * * * * $DEX /var/www/backend/bot/cron_kazan_adjuster.php >> /var/log/bot_kazan.log 2>&1 +*/10 * * * * $DEX /var/www/backend/bot/cron_surge_opportunity.php >> /var/log/bot_surge.log 2>&1 +*/16 * * * * $DEX /var/www/backend/bot/cron_generate_heatmap_cache.php >> /var/log/bot_heatmap.log 2>&1 +0 * * * * $DEX /var/www/backend/bot/cron_seasonal_pricing.php >> /var/log/bot_seasonal.log 2>&1 +0 * * * * $DEX /var/www/backend/bot/cron_predictive_demand.php >> /var/log/bot_demand.log 2>&1 +0 */3 * * * $DEX /var/www/backend/bot/cron_ai_engine.php >> /var/log/bot_ai.log 2>&1 +0 */3 * * * $DEX /var/www/backend/bot/cron_pricing_stability_engine.php >> /var/log/bot_stability.log 2>&1 + +# لقطة لوحة المعلومات — كانت مسجّلة ثلاث مرات (ساعياً + يومياً 3ص + +# أسبوعياً الاثنين 4ص). التشغيل الساعي يغطي الاثنين الآخرين بالكامل، +# وكانت النسخ الثلاث تتسابق على نفس الجداول عند الساعة 3 صباحاً. +0 * * * * $DEX /var/www/backend/bot/cron_dashboard_snapshot.php >> /var/log/bot_snapshot.log 2>&1 + +# ============================================================ +# الإشعارات والتسويق +# ============================================================ +0 * * * * $DEX /var/www/backend/bot/cron_auto_marketing_pusher.php >> /var/log/bot_pusher.log 2>&1 +0 10 * * * $DEX /var/www/backend/bot/cron_passenger_reengagement.php >> /var/log/bot_reengage.log 2>&1 +0 0 * * 0 $DEX /var/www/backend/bot/cron_weekly_health_report.php >> /var/log/bot_health.log 2>&1 + +0 */3 * * * $DEX /var/www/backend/marketing_engine/telegram_scraper.php >> /var/log/mkt_telegram.log 2>&1 +0 */4 * * * $DEX /var/www/backend/marketing_engine/cron_insert_task.php >> /var/log/mkt_tasks.log 2>&1 +0 10 * * 0 $DEX /var/www/backend/marketing_engine/cron_weekly_report.php >> /var/log/mkt_weekly.log 2>&1 + +# ============================================================ +# انتقالي — يُحذف بعد التأكد من مسار worker.php +# ============================================================ +# يفرّغ results.json (مخرجات standalone_worker.php القديم) إلى MySQL. +# بعد انتقال البوت إلى worker.php لن يُكتب هذا الملف، وسيطبع السكربت +# "No results file found" ثم يخرج. أبقِه أسبوعاً لاستيعاب أي نتائج +# متبقّية، ثم احذف السطر. +0 * * * * $DEX /var/www/backend/bot/cron_empty_results_to_db.php >> /var/log/bot_drain.log 2>&1 + +# ============================================================ +# سكربتات موجودة في الكود وغير مجدولة — قرار مطلوب +# ============================================================ +# backend/bot/cron_gemini_advisor.php +# backend/bot/cron_silent_push_inactive.php +# backend/bot/cron_generate_pricing_cache.php +# لم تكن في الجدول القديم. راجعها وقرّر: تُجدول أم تُحذف من الكود. diff --git a/siro_admin/lib/constant/theme.dart b/siro_admin/lib/constant/theme.dart index 45ee420f..c9461c69 100644 --- a/siro_admin/lib/constant/theme.dart +++ b/siro_admin/lib/constant/theme.dart @@ -478,6 +478,30 @@ extension ThemeExtensions on BuildContext { bool get isDark => brightness == Brightness.dark; } +// ─── Semantic Colors on ColorScheme (light/dark adaptive, no BuildContext) ──── +// +// ColorScheme في ماتيريال لا يحتوي خانات لـ success / warning، فكانت الشاشات +// تكتب قيمها الست عشرية مباشرة (0xFF10B981 و 0xFFF59E0B ...). المشكلة أن تلك +// القيم هي نسخة الثيم الداكن وحدها، فتبقى كما هي في الوضع الفاتح ويظهر التصميم +// غير متناسق. +// +// هذا الامتداد يشتق اللون من brightness الخاص بالـ ColorScheme نفسه، فلا يحتاج +// BuildContext ويصلح للاستعمال داخل الدوال المساعدة التي تستقبل `cs` فقط. +// للأماكن التي يتوفر فيها context استخدم AppColors.* أدناه. +extension SemanticColorScheme on ColorScheme { + bool get _isDark => brightness == Brightness.dark; + + Color get success => _isDark ? const Color(0xFF10B981) : const Color(0xFF059669); + Color get successSoft => _isDark ? const Color(0x2610B981) : const Color(0x1A059669); + Color get warning => _isDark ? const Color(0xFFF59E0B) : const Color(0xFFD97706); + Color get warningSoft => _isDark ? const Color(0x26F59E0B) : const Color(0x1AD97706); + Color get danger => _isDark ? const Color(0xFFEF4444) : const Color(0xFFDC2626); + Color get dangerSoft => _isDark ? const Color(0x26EF4444) : const Color(0x1ADC2626); + Color get info => _isDark ? const Color(0xFF3B82F6) : const Color(0xFF2563EB); + Color get infoSoft => _isDark ? const Color(0x263B82F6) : const Color(0x1A2563EB); + Color get textMuted => _isDark ? const Color(0xFF6B7280) : const Color(0xFF9CA3AF); +} + // ─── Semantic Color Getters (light/dark adaptive) ───────────────────────────── // These replace direct AppColor.* usage in views. They automatically adapt // based on the current theme brightness. diff --git a/siro_admin/lib/views/admin/admin_home_page.dart b/siro_admin/lib/views/admin/admin_home_page.dart index 58c2bf37..336b039c 100644 --- a/siro_admin/lib/views/admin/admin_home_page.dart +++ b/siro_admin/lib/views/admin/admin_home_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; @@ -502,6 +503,167 @@ class _AdminHomePageState extends State ); } + /// توزيع الرحلات على فترات اليوم (صباح / مساء / ليل). + /// الحدود مطابقة لما يحسبه backend/Admin/dashbord.php: + /// صباحاً 6–11، مساءً 12–17، ليلاً 18–5. + Widget _buildPeakHoursCard( + double morning, double evening, double night, ColorScheme cs) { + final total = morning + evening + night; + final maxVal = [morning, evening, night].reduce((a, b) => a > b ? a : b); + final maxY = maxVal > 0 ? (maxVal * 1.3).ceilToDouble() : 10.0; + + final slots = [ + (label: 'صباحاً', hint: '6–11', value: morning, color: cs.warning), + (label: 'مساءً', hint: '12–17', value: evening, color: cs.info), + (label: 'ليلاً', hint: '18–5', value: night, color: cs.secondary), + ]; + + return AnimationConfiguration.staggeredList( + position: 1, + duration: const Duration(milliseconds: 450), + child: SlideAnimation( + verticalOffset: 40.0, + child: FadeInAnimation( + child: Container( + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.schedule_rounded, color: cs.primary, size: 18), + const SizedBox(width: 8), + Text('توزيع الرحلات حسب الوقت', + style: TextStyle( + color: cs.onSurface, + fontSize: 13, + fontWeight: FontWeight.w600)), + const Spacer(), + if (total > 0) + Text('${total.toInt()} رحلة', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 11)), + ], + ), + const SizedBox(height: 16), + SizedBox( + height: 140, + child: Directionality( + textDirection: TextDirection.ltr, + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: maxY, + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + getTooltipColor: (_) => cs.surfaceContainerHighest, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + final pct = total > 0 + ? (rod.toY / total * 100).toStringAsFixed(1) + : '0.0'; + return BarTooltipItem( + '${rod.toY.toInt()} ($pct%)', + TextStyle( + color: cs.onSurface, + fontSize: 11, + fontWeight: FontWeight.w600), + ); + }, + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (v) => FlLine( + color: cs.outline.withValues(alpha: 0.5), + strokeWidth: 1), + ), + borderData: FlBorderData(show: false), + titlesData: FlTitlesData( + show: true, + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 34, + getTitlesWidget: (v, _) => Text( + v >= 1000 + ? '${(v / 1000).toStringAsFixed(0)}k' + : v.toInt().toString(), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 9), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 34, + getTitlesWidget: (value, meta) { + final i = value.toInt(); + if (i < 0 || i >= slots.length) { + return const SizedBox(); + } + return Padding( + padding: const EdgeInsets.only(top: 6), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(slots[i].label, + style: TextStyle( + color: cs.onSurface, + fontSize: 10, + fontWeight: FontWeight.w600)), + Text(slots[i].hint, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 8)), + ], + ), + ); + }, + ), + ), + ), + barGroups: [ + for (var i = 0; i < slots.length; i++) + BarChartGroupData( + x: i, + barRods: [ + BarChartRodData( + toY: slots[i].value, + color: slots[i].color, + width: 26, + borderRadius: BorderRadius.circular(6), + backDrawRodData: BackgroundBarChartRodData( + show: true, + toY: maxY, + color: cs.outline.withValues(alpha: 0.25), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } + Widget _buildChartsSection( dynamic data, DashboardController controller, ColorScheme cs) { final completed = (data['completed_rides'] ?? 0).toDouble(); @@ -520,6 +682,12 @@ class _AdminHomePageState extends State final driverCount = (data['countDriver'] ?? 0).toDouble(); final monthRides = (data['countRideThisMonth'] ?? 0).toDouble(); + // توزيع الرحلات على أوقات اليوم — تعيده dashbord.php منذ البداية + // (morning/evening/night_ride_count) ولم يكن معروضاً في أي شاشة. + final morningRides = (data['morning_ride_count'] ?? 0).toDouble(); + final eveningRides = (data['evening_ride_count'] ?? 0).toDouble(); + final nightRides = (data['night_ride_count'] ?? 0).toDouble(); + return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( @@ -677,7 +845,7 @@ class _AdminHomePageState extends State barRods: [ BarChartRodData( toY: speed, - color: const Color(0xFFF59E0B), + color: cs.warning, width: 28, borderRadius: const BorderRadius.vertical(top: Radius.circular(6)), ), @@ -828,6 +996,8 @@ class _AdminHomePageState extends State ), ), const SizedBox(height: 12), + _buildPeakHoursCard(morningRides, eveningRides, nightRides, cs), + const SizedBox(height: 12), AnimationConfiguration.staggeredList( position: 2, duration: const Duration(milliseconds: 450), @@ -1139,15 +1309,16 @@ class _AdminHomePageState extends State } List _getAllActionCategories() { + final cs = Theme.of(context).colorScheme; return [ ActionCategory( title: 'المستخدمين', items: [ - ActionItem('الركاب', Icons.people_outline_rounded, const Color(0xFF3B82F6), + ActionItem('الركاب', Icons.people_outline_rounded, cs.info, () => Get.to(() => Passengrs())), - ActionItem('السائقون', Icons.drive_eta_rounded, const Color(0xFFF59E0B), + ActionItem('السائقون', Icons.drive_eta_rounded, cs.warning, () => Get.to(() => CaptainsPage())), - ActionItem('المراقب', Icons.track_changes_rounded, const Color(0xFFEF4444), + ActionItem('المراقب', Icons.track_changes_rounded, cs.danger, () => Get.to(() => SiroTrackerScreen())), ], ), @@ -1163,11 +1334,11 @@ class _AdminHomePageState extends State items: [ ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, const Color(0xFF6366F1), () => Get.toNamed('/promo')), - ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, const Color(0xFFF59E0B), + ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, cs.warning, () => Get.toNamed('/kazan')), - ActionItem('الشكاوى', Icons.report_problem_rounded, const Color(0xFFEF4444), + ActionItem('الشكاوى', Icons.report_problem_rounded, cs.danger, () => Get.toNamed('/complaints')), - ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, const Color(0xFF3B82F6), + ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, cs.info, () => Get.toNamed('/driver-docs')), ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA), () => Get.toNamed('/social-intelligence')), @@ -1188,7 +1359,7 @@ class _AdminHomePageState extends State await Get.put(StaticController()).getAll(); Get.to(() => const StaticDash()); }), - ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, const Color(0xFF3B82F6), + ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, cs.info, () => Get.to(() => const AdvancedAnalyticsPage())), ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded, const Color(0xFF00CEC9), @@ -1198,7 +1369,7 @@ class _AdminHomePageState extends State ActionCategory( title: 'الجودة والدعم', items: [ - ActionItem('القائمة السوداء', Icons.block_flipped, const Color(0xFFEF4444), + ActionItem('القائمة السوداء', Icons.block_flipped, cs.danger, () => Get.to(() => const BlacklistPage())), ], ), @@ -1210,10 +1381,10 @@ class _AdminHomePageState extends State const Color(0xFF6366F1), () => Get.to(() => const FinancialV2Page())), ActionItem('المحفظة', Icons.account_balance_wallet_rounded, const Color(0xFF6366F1), () => Get.to(() => Wallet())), - ActionItem('هدية 300', Icons.card_giftcard_rounded, const Color(0xFFF59E0B), + ActionItem('هدية 300', Icons.card_giftcard_rounded, cs.warning, () => Get.to(() => DriverGiftCheckPage())), ActionItem('الفواتير', Icons.receipt_long_rounded, - const Color(0xFF10B981), () => Get.to(() => InvoiceListPage())), + cs.success, () => Get.to(() => InvoiceListPage())), ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFF9CA3AF), () => Get.to(() => EmployeePage())), ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, const Color(0xFF6366F1), @@ -1225,11 +1396,11 @@ class _AdminHomePageState extends State title: 'النظام والتواصل', items: [ ActionItem('سجل العمليات', Icons.admin_panel_settings_rounded, - const Color(0xFFEF4444), () => Get.to(() => const AuditLogsPage())), + cs.danger, () => Get.to(() => const AuditLogsPage())), ActionItem('واتساب جماعي', Icons.message_rounded, const Color(0xFF25D366), () => _showWhatsAppDialog(context)), ActionItem('أتمتة التسويق', Icons.campaign_rounded, - const Color(0xFF10B981), () => Get.toNamed('/marketing')), + cs.success, () => Get.toNamed('/marketing')), ActionItem( 'إشعار سائقين', Icons.notifications_active_rounded, @@ -1242,7 +1413,7 @@ class _AdminHomePageState extends State const Color(0xFFF06292), () => Get.put(NotificationController()) .sendNotificationPassengers()), - ActionItem('تسجيل سائق', Icons.person_add_rounded, const Color(0xFF3B82F6), + ActionItem('تسجيل سائق', Icons.person_add_rounded, cs.info, () => Get.to(() => DriversPendingPage())), ActionItem( 'تحديث التطبيق', @@ -1251,7 +1422,7 @@ class _AdminHomePageState extends State () => Get.to(() => PackageUpdateScreen())), ActionItem('مراقب السيرفر', Icons.dns_rounded, const Color(0xFF6366F1), () => Get.to(() => ServerMonitorPage())), - ActionItem('سجل الأخطاء', Icons.error_outline_rounded, const Color(0xFFEF4444), + ActionItem('سجل الأخطاء', Icons.error_outline_rounded, cs.danger, () => Get.to(() => ErrorListPage())), ActionItem( 'أداة التشفير', @@ -1275,7 +1446,7 @@ class _AdminHomePageState extends State ActionItem( 'إضافة خدمة عملاء', Icons.support_agent_rounded, - const Color(0xFF3B82F6), + cs.info, () => Get.to(() => const AddStaffPage(role: 'service')), ), ], @@ -1297,25 +1468,25 @@ class _AdminHomePageState extends State 'title': 'مكتملة', 'value': data['completed_rides'], 'icon': Icons.check_circle_rounded, - 'color': const Color(0xFF10B981), + 'color': cs.success, }, { 'title': 'ملغاة', 'value': data['cancelled_rides'], 'icon': Icons.cancel_rounded, - 'color': const Color(0xFFEF4444), + 'color': cs.danger, }, { 'title': 'مدفوعات', 'value': _formatCurrency(data['payments']), 'icon': Icons.attach_money_rounded, - 'color': const Color(0xFFF59E0B), + 'color': cs.warning, }, { 'title': 'Comfort', 'value': data['comfort'], 'icon': Icons.chair_rounded, - 'color': const Color(0xFF10B981), + 'color': cs.success, }, { 'title': 'Speed', diff --git a/siro_admin/lib/views/admin/analytics/live_analytics_page.dart b/siro_admin/lib/views/admin/analytics/live_analytics_page.dart index 8da8287c..a9404bda 100644 --- a/siro_admin/lib/views/admin/analytics/live_analytics_page.dart +++ b/siro_admin/lib/views/admin/analytics/live_analytics_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -342,7 +343,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin { label: 'إيرادات اليوم', value: _fmt(revToday), sub: '${revChange >= 0 ? '+' : ''}${revChange.toStringAsFixed(1)}% عن الأمس', - subColor: revChange >= 0 ? const Color(0xFF10B981) : cs.error, + subColor: revChange >= 0 ? cs.success : cs.error, icon: Icons.attach_money_rounded, ), _StatCard( @@ -380,7 +381,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin { (v) => setState(() => showGap = v)), _LayerChip('كثافة الطلب', showHeatmap, Colors.orange, (v) => setState(() => showHeatmap = v)), - _LayerChip('مواقع الكباتن', showSupply, const Color(0xFF10B981), + _LayerChip('مواقع الكباتن', showSupply, cs.success, (v) => setState(() => showSupply = v)), _LayerChip('أسعار المنافسين', showPricing, cs.tertiary, (v) => setState(() => showPricing = v)), @@ -409,7 +410,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin { point: LatLng(lat, lng), color: gap > 0 ? Colors.redAccent.withValues(alpha: 0.3 + intensity * 0.5) - : const Color(0xFF10B981).withValues(alpha: 0.4), + : cs.success.withValues(alpha: 0.4), borderStrokeWidth: 0, radius: 12 + intensity * 8, )); @@ -442,7 +443,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin { if (lat == 0 || lng == 0) continue; markers.add(CircleMarker( point: LatLng(lat, lng), - color: const Color(0xFF10B981).withValues(alpha: 0.5), + color: cs.success.withValues(alpha: 0.5), borderStrokeWidth: 1, borderColor: Colors.white.withValues(alpha: 0.4), radius: 6 + supply.clamp(0, 10).toDouble(), @@ -561,7 +562,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin { if (statuses.isEmpty) return const SizedBox.shrink(); final colors = { - 'Finished': const Color(0xFF10B981), + 'Finished': cs.success, 'wait': cs.tertiary, 'started': cs.tertiary, 'arrived': cs.primary, @@ -701,7 +702,7 @@ class _MarketTab extends StatelessWidget { cs.error, cs.tertiary, cs.tertiary, - const Color(0xFF10B981), + cs.success, cs.primary, ]; final hours = {}; @@ -940,7 +941,7 @@ class _FleetTab extends StatelessWidget { cs.tertiary, cs.tertiary, cs.primary, - const Color(0xFF10B981), + cs.success, cs.onSurfaceVariant, const Color(0xFFfd79a8), ]; @@ -994,7 +995,7 @@ class _FleetTab extends StatelessWidget { return FlSpot( e.key.toDouble(), _toDouble(e.value['company_profit'])); }).toList(), - color: const Color(0xFF10B981), + color: cs.success, barWidth: 2, dotData: const FlDotData(show: false), isCurved: true, @@ -1109,7 +1110,7 @@ class _GrowthTab extends StatelessWidget { final pct = ((last - prev) / prev * 100).toStringAsFixed(1); weekChange = '${double.parse(pct) >= 0 ? '+' : ''}$pct% عن الأسبوع السابق'; weekColor = - double.parse(pct) >= 0 ? const Color(0xFF10B981) : cs.error; + double.parse(pct) >= 0 ? cs.success : cs.error; } } 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 66946f56..3fade765 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 @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/controller/drivers/driver_not_active_controller.dart'; @@ -75,13 +76,13 @@ Expanded( Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + color: cs.warning.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + color: cs.warning.withValues(alpha: 0.25)), ), child: Icon(Icons.hourglass_top_rounded, - color: const Color(0xFFF59E0B), size: 18), + color: cs.warning, size: 18), ), const SizedBox(width: 10), Text( @@ -171,19 +172,19 @@ Expanded( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - const Color(0xFFF59E0B).withValues(alpha: 0.20), - const Color(0xFFF59E0B).withValues(alpha: 0.08), + cs.warning.withValues(alpha: 0.20), + cs.warning.withValues(alpha: 0.08), ], ), borderRadius: BorderRadius.circular(14), border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.2)), + color: cs.warning.withValues(alpha: 0.2)), ), child: Center( child: Text( fullName.isNotEmpty ? fullName[0].toUpperCase() : 'D', - style: const TextStyle( - color: Color(0xFFF59E0B), + style: TextStyle( + color: cs.warning, fontWeight: FontWeight.w800, fontSize: 18, ), @@ -210,7 +211,7 @@ Expanded( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: const Color(0xFFF59E0B) + color: cs.warning .withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), @@ -219,14 +220,14 @@ Expanded( children: [ Icon(Icons.hourglass_top_rounded, size: 11, - color: const Color(0xFFF59E0B)), + color: cs.warning), const SizedBox(width: 3), - const Text( + Text( 'معلق', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, - color: Color(0xFFF59E0B), + color: cs.warning, ), ), ], diff --git a/siro_admin/lib/views/admin/complaints/complaint_list_page.dart b/siro_admin/lib/views/admin/complaints/complaint_list_page.dart index 4d830ed2..41779954 100644 --- a/siro_admin/lib/views/admin/complaints/complaint_list_page.dart +++ b/siro_admin/lib/views/admin/complaints/complaint_list_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; @@ -249,7 +250,7 @@ class ComplaintListPage extends StatelessWidget { switch (status) { case 'Open': return cs.error; case 'In Progress': return cs.tertiary; - case 'Resolved': return const Color(0xFF10B981); + case 'Resolved': return cs.success; default: return cs.onSurfaceVariant; } } diff --git a/siro_admin/lib/views/admin/dashboard_v2_widget.dart b/siro_admin/lib/views/admin/dashboard_v2_widget.dart index 2c4c3792..25b35cec 100644 --- a/siro_admin/lib/views/admin/dashboard_v2_widget.dart +++ b/siro_admin/lib/views/admin/dashboard_v2_widget.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart'; @@ -85,13 +86,13 @@ class DashboardV2Widget extends StatelessWidget { 'title': 'سائقون أونلاين', 'value': data['online_drivers']?.toString() ?? '0', 'icon': Icons.wifi_tethering, - 'color': const Color(0xFF10B981), + 'color': cs.success, }, { 'title': 'إيرادات اليوم', 'value': '${data['revenue_today'] ?? 0}', 'icon': Icons.monetization_on_rounded, - 'color': const Color(0xFFF59E0B), + 'color': cs.warning, }, { 'title': 'إيرادات الأمس', @@ -225,7 +226,7 @@ class DashboardV2Widget extends StatelessWidget { case 'high': return cs.error; case 'medium': - return const Color(0xFFF59E0B); + return cs.warning; case 'warning': return const Color(0xFFF97316); default: diff --git a/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart b/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart index d34f186c..2696b809 100644 --- a/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart +++ b/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; @@ -150,7 +151,7 @@ class DriverDocsReviewPage extends StatelessWidget { MyElevatedButton( title: 'اعتماد وتفعيل الحساب', icon: Icons.check_circle_rounded, - kolor: const Color(0xFF10B981), + kolor: cs.success, onPressed: () async { bool success = await controller.approveDriver(id); if (success) { diff --git a/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart b/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart index 15eb3653..a8e5e03a 100644 --- a/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart +++ b/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/controller/functions/crud.dart'; @@ -139,13 +140,13 @@ class DriverGiftCheckPage extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + color: cs.warning.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + color: cs.warning.withValues(alpha: 0.25)), ), child: Icon(Icons.card_giftcard_rounded, - color: const Color(0xFFF59E0B), size: 18), + color: cs.warning, size: 18), ), const SizedBox(width: 10), Text( @@ -184,16 +185,16 @@ class DriverGiftCheckPage extends StatelessWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - const Color(0xFFF59E0B).withValues(alpha: 0.20), - const Color(0xFFF59E0B).withValues(alpha: 0.08), + cs.warning.withValues(alpha: 0.20), + cs.warning.withValues(alpha: 0.08), ], ), shape: BoxShape.circle, border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + color: cs.warning.withValues(alpha: 0.25)), ), - child: const Icon(Icons.card_giftcard_rounded, - size: 40, color: Color(0xFFF59E0B)), + child: Icon(Icons.card_giftcard_rounded, + size: 40, color: cs.warning), ), const SizedBox(height: 16), Text( @@ -244,7 +245,7 @@ class DriverGiftCheckPage extends StatelessWidget { ? null : () => controller.processDriverGift(), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFF59E0B), + backgroundColor: cs.warning, foregroundColor: Colors.white, disabledBackgroundColor: cs.outline, shape: RoundedRectangleBorder( diff --git a/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart b/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart index a78b4de4..39b8c734 100644 --- a/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart +++ b/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; @@ -210,7 +211,7 @@ class _SiroTrackerScreenState extends State shape: BoxShape.circle, gradient: LinearGradient( colors: isLiveMode - ? [const Color(0xFF10B981), const Color(0xFF10B981)] + ? [cs.success, cs.success] : [cs.tertiary, cs.tertiary], begin: Alignment.topLeft, end: Alignment.bottomRight, @@ -218,7 +219,7 @@ class _SiroTrackerScreenState extends State boxShadow: [ BoxShadow( color: (isLiveMode - ? const Color(0xFF10B981) + ? cs.success : cs.tertiary) .withValues(alpha: 0.5), blurRadius: 12, @@ -281,7 +282,7 @@ class _SiroTrackerScreenState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: isLiveMode - ? [const Color(0xFF10B981), const Color(0xFF10B981)] + ? [cs.success, cs.success] : [cs.tertiary, cs.tertiary], ), borderRadius: BorderRadius.circular(12), @@ -417,7 +418,7 @@ class _SiroTrackerScreenState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildStatItem("✓ مكتملة", completed, const Color(0xFF10B981), cs), + _buildStatItem("✓ مكتملة", completed, cs.success, cs), Container( width: 1, height: 40, @@ -635,7 +636,7 @@ class _SiroTrackerScreenState extends State setState(() => isLiveMode = true); fetchData(); }, - const Color(0xFF10B981), + cs.success, cs, ), ), @@ -646,7 +647,7 @@ class _SiroTrackerScreenState extends State icon: Icons.live_tv, label: "نشط الآن (مباشر)", value: liveCount.toString(), - color: const Color(0xFF10B981), + color: cs.success, cs: cs, ), const SizedBox(height: 12), @@ -685,7 +686,7 @@ class _SiroTrackerScreenState extends State size: 14, color: isLoading ? cs.tertiary - : const Color(0xFF10B981), + : cs.success, ), ], ), diff --git a/siro_admin/lib/views/admin/drivers/monitor_ride.dart b/siro_admin/lib/views/admin/drivers/monitor_ride.dart index cd75c1f8..7773f24d 100644 --- a/siro_admin/lib/views/admin/drivers/monitor_ride.dart +++ b/siro_admin/lib/views/admin/drivers/monitor_ride.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -428,14 +429,14 @@ class RideMonitorScreen extends StatelessWidget { point: controller.startPoint.value!, width: 30, height: 30, - child: _buildPointMarker(const Color(0xFF10B981), cs), + child: _buildPointMarker(cs.success, cs), ), if (controller.endPoint.value != null) Marker( point: controller.endPoint.value!, width: 30, height: 30, - child: _buildPointMarker(const Color(0xFFEF4444), cs), + child: _buildPointMarker(cs.danger, cs), ), if (controller.driverLocation.value != null) Marker( @@ -666,8 +667,8 @@ class RideMonitorScreen extends StatelessWidget { color: controller.rideStatus.value .toLowerCase() == 'begin' - ? const Color(0xFF10B981) - : const Color(0xFFF59E0B), + ? cs.success + : cs.warning, ), ), const SizedBox(width: 6), @@ -697,7 +698,7 @@ class RideMonitorScreen extends StatelessWidget { _buildModernInfoBadge( Icons.speed_rounded, "${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س", - const Color(0xFF3B82F6), + cs.info, cs, ), Container( diff --git a/siro_admin/lib/views/admin/employee/employee_page.dart b/siro_admin/lib/views/admin/employee/employee_page.dart index 8932c834..25d679fc 100644 --- a/siro_admin/lib/views/admin/employee/employee_page.dart +++ b/siro_admin/lib/views/admin/employee/employee_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/constant/links.dart'; @@ -131,7 +132,7 @@ class _EmployeeCard extends StatelessWidget { @override Widget build(BuildContext context) { bool isExcellent = employee['status'].toString().contains('ممتاز'); - Color statusColor = isExcellent ? const Color(0xFF10B981) : Colors.amber; + Color statusColor = isExcellent ? cs.success : Colors.amber; return Container( margin: const EdgeInsets.only(bottom: 16), @@ -234,7 +235,7 @@ class _EmployeeCard extends StatelessWidget { const SizedBox(width: 16), Material( - color: const Color(0xFF10B981).withValues(alpha: 0.1), + color: cs.success.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), child: InkWell( onTap: () => @@ -242,8 +243,8 @@ class _EmployeeCard extends StatelessWidget { borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(14), - child: const Icon(Icons.call, - color: Color(0xFF10B981), size: 24), + child: Icon(Icons.call, + color: cs.success, size: 24), ), ), ), diff --git a/siro_admin/lib/views/admin/error/error/error_page.dart b/siro_admin/lib/views/admin/error/error/error_page.dart index 1a6c3459..89c942b2 100644 --- a/siro_admin/lib/views/admin/error/error/error_page.dart +++ b/siro_admin/lib/views/admin/error/error/error_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/constant/links.dart'; @@ -224,10 +225,10 @@ class _ErrorListPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( + Icon( Icons.check_circle_outline, size: 64, - color: Color(0xFF10B981), + color: cs.success, ), const SizedBox(height: 16), Text( @@ -418,13 +419,13 @@ class _ErrorTile extends StatelessWidget { item.userType.toLowerCase().contains('سائق'); final userTypeColor = - isDriver ? const Color(0xFF10B981) : const Color(0xFFF59E0B); + isDriver ? cs.success : cs.warning; final userTypeIcon = isDriver ? Icons.directions_car : Icons.person; final userTypeLabel = isDriver ? "سائق" : "راكب"; final userTypeBgColor = isDriver - ? const Color(0xFF10B981).withValues(alpha: 0.1) - : const Color(0xFFF59E0B).withValues(alpha: 0.1); + ? cs.success.withValues(alpha: 0.1) + : cs.warning.withValues(alpha: 0.1); return Container( margin: const EdgeInsets.only(bottom: 12), @@ -453,7 +454,7 @@ class _ErrorTile extends StatelessWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - const Color(0xFFEF4444), + cs.danger, cs.error, ], begin: Alignment.centerLeft, diff --git a/siro_admin/lib/views/admin/financial/financial_v2_page.dart b/siro_admin/lib/views/admin/financial/financial_v2_page.dart index bd1040b8..994e4e10 100644 --- a/siro_admin/lib/views/admin/financial/financial_v2_page.dart +++ b/siro_admin/lib/views/admin/financial/financial_v2_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/controller/admin/financial_v2_controller.dart'; @@ -120,7 +121,7 @@ class FinancialV2Page extends StatelessWidget { 'إجمالي الإيرادات', '${stats['total_revenue'] ?? 0}', Icons.payments_rounded, - const Color(0xFF10B981), + cs.success, cs, isSmall: true, ), diff --git a/siro_admin/lib/views/admin/marketing/marketing_page.dart b/siro_admin/lib/views/admin/marketing/marketing_page.dart index 3281c878..44242aa9 100644 --- a/siro_admin/lib/views/admin/marketing/marketing_page.dart +++ b/siro_admin/lib/views/admin/marketing/marketing_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get/get.dart'; @@ -211,7 +212,7 @@ class MarketingPage extends StatelessWidget { const SizedBox(height: 12), Row( children: [ - _buildStatItem('سعرنا', '${anomaly['our_price']}', const Color(0xFF10B981), cs), + _buildStatItem('سعرنا', '${anomaly['our_price']}', cs.success, cs), const SizedBox(width: 24), _buildStatItem('سعر المنافس', '${anomaly['competitor_price']}', cs.error, cs), const SizedBox(width: 24), @@ -441,7 +442,7 @@ class MarketingPage extends StatelessWidget { child: Text(region['name'] as String, style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)), ), const SizedBox(width: 8), - Text(region['desc'] as String, style: TextStyle(fontSize: 10, color: (region['pci'] as double) < 1 ? const Color(0xFF10B981) : cs.error)), + Text(region['desc'] as String, style: TextStyle(fontSize: 10, color: (region['pci'] as double) < 1 ? cs.success : cs.error)), ], ), const SizedBox(height: 6), @@ -449,8 +450,8 @@ class MarketingPage extends StatelessWidget { borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: ((region['weight'] as double) + 1) / 2, - backgroundColor: const Color(0xFF10B981).withValues(alpha: 0.3), - color: (region['pci'] as double) < 1 ? const Color(0xFF10B981) : cs.error, + backgroundColor: cs.success.withValues(alpha: 0.3), + color: (region['pci'] as double) < 1 ? cs.success : cs.error, minHeight: 6, ), ), @@ -518,7 +519,7 @@ class MarketingPage extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: c.simulatorRecommendationStatus == 'success' ? const Color(0xFF10B981).withValues(alpha: 0.1) : (c.simulatorRecommendationStatus == 'danger' ? cs.error.withValues(alpha: 0.1) : cs.tertiary.withValues(alpha: 0.1)), + color: c.simulatorRecommendationStatus == 'success' ? cs.success.withValues(alpha: 0.1) : (c.simulatorRecommendationStatus == 'danger' ? cs.error.withValues(alpha: 0.1) : cs.tertiary.withValues(alpha: 0.1)), borderRadius: BorderRadius.circular(8), ), child: Column( @@ -544,7 +545,7 @@ class MarketingPage extends StatelessWidget { c.simulatorRecommendationMessage ?? '', style: TextStyle( fontSize: 11, - color: c.simulatorRecommendationStatus == 'success' ? const Color(0xFF10B981) : (c.simulatorRecommendationStatus == 'danger' ? cs.error : cs.tertiary) + color: c.simulatorRecommendationStatus == 'success' ? cs.success : (c.simulatorRecommendationStatus == 'danger' ? cs.error : cs.tertiary) ), ), ], @@ -570,7 +571,7 @@ class MarketingPage extends StatelessWidget { final channel = log['channel']?.toString().toUpperCase() ?? 'SMS'; Color channelColor = cs.tertiary; - if (channel == 'SMS') channelColor = const Color(0xFF10B981); + if (channel == 'SMS') channelColor = cs.success; if (channel == 'WHATSAPP') channelColor = const Color(0xFF25D366); return Card( @@ -689,7 +690,7 @@ class MarketingPage extends StatelessWidget { children: [ _buildTelemetryItem('طلبات الـ API', '${c.apiRequestsCount}', cs.onSurface, cs), _buildTelemetryItem('الرموز (Tokens)', '${c.totalTokensUsed}', cs.onSurface, cs), - _buildTelemetryItem('التكلفة التقديرية', '\$${c.estimatedCostUSD.toStringAsFixed(5)}', const Color(0xFF10B981), cs), + _buildTelemetryItem('التكلفة التقديرية', '\$${c.estimatedCostUSD.toStringAsFixed(5)}', cs.success, cs), ], ), ], diff --git a/siro_admin/lib/views/admin/passenger/passenger.dart b/siro_admin/lib/views/admin/passenger/passenger.dart index 1a5986f8..619c9b14 100644 --- a/siro_admin/lib/views/admin/passenger/passenger.dart +++ b/siro_admin/lib/views/admin/passenger/passenger.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -205,7 +206,7 @@ class Passengrs extends StatelessWidget { fontWeight: FontWeight.bold, fontSize: 13)), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFFF59E0B), + backgroundColor: cs.warning, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14)), elevation: 0, @@ -391,23 +392,23 @@ class Passengrs extends StatelessWidget { padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: const Color(0xFFF59E0B) + color: cs.warning .withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.star_rounded, - size: 12, color: Color(0xFFF59E0B)), + Icon(Icons.star_rounded, + size: 12, color: cs.warning), const SizedBox(width: 3), Text( user['ratingPassenger']?.toString() ?? '0.0', - style: const TextStyle( + style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, - color: Color(0xFFF59E0B), + color: cs.warning, ), ), ], diff --git a/siro_admin/lib/views/admin/promo/promo_management_page.dart b/siro_admin/lib/views/admin/promo/promo_management_page.dart index a11fc1d4..fe507ba9 100644 --- a/siro_admin/lib/views/admin/promo/promo_management_page.dart +++ b/siro_admin/lib/views/admin/promo/promo_management_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; @@ -112,9 +113,9 @@ class PromoManagementPage extends StatelessWidget { const SizedBox(height: 4), Row( children: [ - Icon(Icons.money_rounded, size: 14, color: const Color(0xFF10B981)), + Icon(Icons.money_rounded, size: 14, color: cs.success), const SizedBox(width: 4), - Text('% ${promo['amount']}', style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, color: const Color(0xFF10B981))), + Text('% ${promo['amount']}', style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, color: cs.success)), const SizedBox(width: 12), Icon(Icons.person_rounded, size: 14, color: cs.tertiary), const SizedBox(width: 4), diff --git a/siro_admin/lib/views/admin/quality/blacklist_page.dart b/siro_admin/lib/views/admin/quality/blacklist_page.dart index 52253c88..aded5c33 100644 --- a/siro_admin/lib/views/admin/quality/blacklist_page.dart +++ b/siro_admin/lib/views/admin/quality/blacklist_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../controller/admin/quality_controller.dart'; @@ -304,12 +305,12 @@ class BlacklistPage extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: const Color(0xFF10B981).withValues(alpha: 0.1), + color: cs.success.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(10), - border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.2)), + border: Border.all(color: cs.success.withValues(alpha: 0.2)), ), child: Icon(Icons.settings_backup_restore_rounded, - color: const Color(0xFF10B981), size: 18), + color: cs.success, size: 18), ), ), ], @@ -329,7 +330,7 @@ class BlacklistPage extends StatelessWidget { textConfirm: "نعم، فك الحظر", textCancel: "تراجع", confirmTextColor: Colors.white, - buttonColor: const Color(0xFF10B981), + buttonColor: cs.success, cancelTextColor: cs.onSurfaceVariant, onConfirm: () { Get.back(); 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 a7676d1a..0e530ca4 100644 --- a/siro_admin/lib/views/admin/rides/ride_lookup_page.dart +++ b/siro_admin/lib/views/admin/rides/ride_lookup_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -418,9 +419,9 @@ class _RidesDashboardScreenState extends State 'جارية', controller.beginCount.toString(), Icons.directions_car_rounded, - const Color(0xFF10B981), cs)), + cs.success, cs)), Obx(() => _buildStatCard('جديدة', controller.newCount.toString(), - Icons.new_releases_rounded, const Color(0xFF3B82F6), cs)), + Icons.new_releases_rounded, cs.info, cs)), Obx(() => _buildStatCard( 'مكتملة', controller.completedCount.toString(), @@ -430,12 +431,12 @@ class _RidesDashboardScreenState extends State 'ملغاة', controller.canceledCount.toString(), Icons.cancel_rounded, - const Color(0xFFEF4444), cs)), + cs.danger, cs)), Obx(() => _buildStatCard( 'الإيرادات', controller.totalRevenue.value.toStringAsFixed(0), Icons.payments_rounded, - const Color(0xFFF59E0B), cs)), + cs.warning, cs)), ], ), ), @@ -624,15 +625,15 @@ class _RidesDashboardScreenState extends State children: [ Column( children: [ - const Icon(Icons.my_location_rounded, - size: 14, color: Color(0xFF10B981)), + Icon(Icons.my_location_rounded, + size: 14, color: cs.success), Container( width: 2, height: 16, color: Colors.grey.withOpacity(0.3), margin: const EdgeInsets.symmetric(vertical: 2)), - const Icon(Icons.location_on_rounded, - size: 14, color: Color(0xFFEF4444)), + Icon(Icons.location_on_rounded, + size: 14, color: cs.danger), ], ), const SizedBox(width: 10), @@ -682,7 +683,7 @@ class _RidesDashboardScreenState extends State title: 'السائق', name: ride.driverName, phone: ride.driverPhone, - color: const Color(0xFF3B82F6), + color: cs.info, isAdmin: isAdmin, ), const Padding( @@ -709,19 +710,19 @@ class _RidesDashboardScreenState extends State _buildInfoChip( Icons.payments_rounded, '${double.tryParse(ride.price)?.toStringAsFixed(0) ?? 0} ل.س', - const Color(0xFF10B981)), + cs.success), const SizedBox(width: 8), _buildInfoChip( Icons.straighten_rounded, '${double.tryParse(ride.distance)?.toStringAsFixed(1) ?? 0} كم', - const Color(0xFFF59E0B)), + cs.warning), const SizedBox(width: 8), _buildInfoChip( Icons.access_time_rounded, ride.time.length > 5 ? ride.time.substring(0, 5) : ride.time, - const Color(0xFF3B82F6)), + cs.info), ], ), @@ -735,16 +736,16 @@ class _RidesDashboardScreenState extends State width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: const Color(0xFFEF4444).withOpacity(0.08), + color: cs.danger.withOpacity(0.08), borderRadius: BorderRadius.circular(8), border: Border.all( - color: const Color(0xFFEF4444).withOpacity(0.2)), + color: cs.danger.withOpacity(0.2)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.info_outline_rounded, - size: 16, color: Color(0xFFEF4444)), + Icon(Icons.info_outline_rounded, + size: 16, color: cs.danger), const SizedBox(width: 6), Expanded( child: Text( @@ -859,14 +860,15 @@ class _RidesDashboardScreenState extends State // Helper Methods for Status Color _getStatusColor(String status) { + final cs = Theme.of(context).colorScheme; if (status == 'Begin' || status == 'Arrived') { - return const Color(0xFF10B981); + return cs.success; } if (status == 'Finished') return const Color(0xFF14B8A6); if (status.contains('Cancel') || status == 'TimeOut') { - return const Color(0xFFEF4444); + return cs.danger; } - if (status == 'New') return const Color(0xFF3B82F6); + if (status == 'New') return cs.info; return Colors.grey; } @@ -976,6 +978,7 @@ class _RideMapMonitorScreenState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( appBar: AppBar( title: Text( @@ -1031,8 +1034,8 @@ class _RideMapMonitorScreenState extends State { blurRadius: 5), ], ), - child: const Icon(Icons.flag_rounded, - color: Color(0xFF10B981), size: 24), + child: Icon(Icons.flag_rounded, + color: cs.success, size: 24), ), alignment: Alignment.topCenter, ), @@ -1052,8 +1055,8 @@ class _RideMapMonitorScreenState extends State { blurRadius: 5), ], ), - child: const Icon(Icons.location_on_rounded, - color: Color(0xFFEF4444), size: 24), + child: Icon(Icons.location_on_rounded, + color: cs.danger, size: 24), ), alignment: Alignment.topCenter, ), @@ -1069,12 +1072,12 @@ class _RideMapMonitorScreenState extends State { shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: const Color(0xFF3B82F6).withOpacity(0.3), + color: cs.info.withOpacity(0.3), blurRadius: 8), ], ), - child: const Icon(Icons.directions_car_rounded, - color: Color(0xFF3B82F6), size: 28), + child: Icon(Icons.directions_car_rounded, + color: cs.info, size: 28), ), ), ], @@ -1110,7 +1113,7 @@ class _RideMapMonitorScreenState extends State { title: 'السائق', name: widget.ride.driverName, phone: widget.ride.driverPhone, - color: const Color(0xFF3B82F6), + color: cs.info, ), const SizedBox(height: 12), // Passenger Info diff --git a/siro_admin/lib/views/admin/rides/rides.dart b/siro_admin/lib/views/admin/rides/rides.dart index 24376237..a61cf1c3 100644 --- a/siro_admin/lib/views/admin/rides/rides.dart +++ b/siro_admin/lib/views/admin/rides/rides.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -303,13 +304,13 @@ class Rides extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + color: cs.warning.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + color: cs.warning.withValues(alpha: 0.25)), ), child: Icon(Icons.analytics_rounded, - color: const Color(0xFFF59E0B), size: 16), + color: cs.warning, size: 16), ), const SizedBox(width: 10), Text( @@ -338,9 +339,9 @@ class Rides extends StatelessWidget { _DetailItem('إجمالي الرحلات', details['total_rides'] ?? '', Icons.route_rounded, cs.primary), _DetailItem('رحلات نشطة', details['ongoing_rides'] ?? '', - Icons.play_circle_rounded, const Color(0xFFF59E0B)), + Icons.play_circle_rounded, cs.warning), _DetailItem('مكتملة', details['completed_rides'] ?? '', - Icons.check_circle_rounded, const Color(0xFF10B981)), + Icons.check_circle_rounded, cs.success), _DetailItem('ملغاة', details['cancelled_rides'] ?? '', Icons.cancel_rounded, cs.error), _DetailItem('أطول مدة', details['longest_duration'] ?? '', @@ -348,17 +349,17 @@ class Rides extends StatelessWidget { _DetailItem('إجمالي المسافة', '${details['total_distance'] ?? 0} km', Icons.straighten_rounded, cs.primary), _DetailItem('متوسط المسافة', '${details['average_distance'] ?? 0} km', - Icons.speed_rounded, const Color(0xFFF59E0B)), + Icons.speed_rounded, cs.warning), _DetailItem('أطول مسافة', '${details['longest_distance'] ?? 0} km', Icons.arrow_forward_rounded, cs.tertiary), _DetailItem('إجمالي أرباح السائقين', '\$${details['total_driver_earnings'] ?? 0}', Icons.payments_rounded, - const Color(0xFF10B981)), + cs.success), _DetailItem('إجمالي أرباح الشركة', '\$${details['total_company_earnings'] ?? 0}', Icons.business_center_rounded, cs.primary), _DetailItem('نسبة الشركة', '${details['companyPercent'] ?? 0}%', - Icons.pie_chart_rounded, const Color(0xFFF59E0B)), + Icons.pie_chart_rounded, cs.warning), ]; return GridView.builder( diff --git a/siro_admin/lib/views/admin/server/monitor_server_page.dart b/siro_admin/lib/views/admin/server/monitor_server_page.dart index b50d3eb0..43e0908c 100644 --- a/siro_admin/lib/views/admin/server/monitor_server_page.dart +++ b/siro_admin/lib/views/admin/server/monitor_server_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../controller/server/server_monitor_controller.dart'; @@ -365,7 +366,7 @@ class _ServicesCard extends StatelessWidget { borderRadius: BorderRadius.circular(12), border: Border.all( color: isActive - ? const Color(0xFF10B981).withValues(alpha: 0.3) + ? cs.success.withValues(alpha: 0.3) : cs.error.withValues(alpha: 0.3), ), ), @@ -389,7 +390,7 @@ class _ServicesCard extends StatelessWidget { const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isActive - ? const Color(0xFF10B981).withValues(alpha: 0.1) + ? cs.success.withValues(alpha: 0.1) : cs.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), diff --git a/siro_admin/lib/views/admin/static/advanced_analytics_page.dart b/siro_admin/lib/views/admin/static/advanced_analytics_page.dart index 4ab91ef8..2b31143e 100644 --- a/siro_admin/lib/views/admin/static/advanced_analytics_page.dart +++ b/siro_admin/lib/views/admin/static/advanced_analytics_page.dart @@ -1,6 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:siro_admin/constant/theme.dart'; import 'package:siro_admin/controller/admin/analytics_v2_controller.dart'; class AdvancedAnalyticsPage extends StatelessWidget { @@ -17,6 +18,9 @@ class AdvancedAnalyticsPage extends StatelessWidget { _buildAppBar(context, cs), Expanded( child: GetBuilder( + init: Get.isRegistered() + ? Get.find() + : AnalyticsV2Controller(), builder: (ctrl) { if (ctrl.isLoading) { return _buildLoadingState(cs); @@ -178,7 +182,7 @@ class AdvancedAnalyticsPage extends StatelessWidget { _buildSummaryCard( 'صافي الربح', '${summary['total_profit_all'] ?? 0}', - const Color(0xFF10B981), + cs.success, Icons.trending_up_rounded, cs, ), @@ -285,7 +289,7 @@ class AdvancedAnalyticsPage extends StatelessWidget { children: [ _LegendDot(color: cs.tertiary, label: 'الإيرادات'), const SizedBox(width: 16), - _LegendDot(color: const Color(0xFF10B981), label: 'الربح'), + _LegendDot(color: cs.success, label: 'الربح'), ], ), const SizedBox(height: 16), @@ -356,13 +360,13 @@ class AdvancedAnalyticsPage extends StatelessWidget { LineChartBarData( spots: profitSpots, isCurved: true, - color: const Color(0xFF10B981), + color: cs.success, barWidth: 3, isStrokeCapRound: true, dotData: const FlDotData(show: false), belowBarData: BarAreaData( show: true, - color: const Color(0xFF10B981).withValues(alpha: 0.08), + color: cs.success.withValues(alpha: 0.08), ), ), ], @@ -418,7 +422,7 @@ class AdvancedAnalyticsPage extends StatelessWidget { ), BarChartRodData( toY: dCount, - color: const Color(0xFFF59E0B).withValues(alpha: 0.7), + color: cs.warning.withValues(alpha: 0.7), width: 8, borderRadius: BorderRadius.circular(4), ), @@ -450,7 +454,7 @@ class AdvancedAnalyticsPage extends StatelessWidget { _LegendDot(color: cs.tertiary, label: 'ركاب جدد'), const SizedBox(width: 16), _LegendDot( - color: const Color(0xFFF59E0B), label: 'سائقين جدد'), + color: cs.warning, label: 'سائقين جدد'), ], ), const SizedBox(height: 16), @@ -532,7 +536,7 @@ class AdvancedAnalyticsPage extends StatelessWidget { final d = drivers[i]; final isTop3 = i < 3; final rankColors = [ - const Color(0xFFF59E0B), + cs.warning, cs.onSurfaceVariant, const Color(0xFFCD7F32), ]; @@ -624,7 +628,7 @@ class AdvancedAnalyticsPage extends StatelessWidget { Text( '${d['total_revenue']} ل.س', style: TextStyle( - color: const Color(0xFF10B981), + color: cs.success, fontSize: 11, fontWeight: FontWeight.w600, ), diff --git a/siro_admin/lib/views/admin/static/static.dart b/siro_admin/lib/views/admin/static/static.dart index 52b41174..ca3bd475 100644 --- a/siro_admin/lib/views/admin/static/static.dart +++ b/siro_admin/lib/views/admin/static/static.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; @@ -62,7 +63,7 @@ class StaticDash extends StatelessWidget { spots: c.chartDataDrivers, compareSpots: c.isComparing ? c.chartDataDriversCompare : null, - color: const Color(0xFFF59E0B), + color: cs.warning, icon: Icons.drive_eta_rounded, controller: c, cs: cs, @@ -146,7 +147,7 @@ class StaticDash extends StatelessWidget { compareSpots: c.isComparing ? c.chartDataDriversMatchingNotesCompare : null, - color: const Color(0xFF10B981), + color: cs.success, icon: Icons.phone_in_talk_rounded, controller: c, cs: cs, @@ -354,7 +355,7 @@ class _KpiRow extends StatelessWidget { _KpiItem('الركاب', controller.totalMonthlyPassengers, Icons.groups_rounded, cs.tertiary), _KpiItem('السائقون', controller.totalMonthlyDrivers, - Icons.drive_eta_rounded, const Color(0xFFF59E0B)), + Icons.drive_eta_rounded, cs.warning), _KpiItem( 'الرحلات', controller.totalMonthlyRides, Icons.map_rounded, cs.primary), ]; @@ -822,13 +823,13 @@ class _EmployeeLeaderboard extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + color: cs.warning.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(9), border: Border.all( - color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + color: cs.warning.withValues(alpha: 0.25)), ), - child: const Icon(Icons.emoji_events_rounded, - color: Color(0xFFF59E0B), size: 16), + child: Icon(Icons.emoji_events_rounded, + color: cs.warning, size: 16), ), const SizedBox(width: 10), Column( @@ -930,7 +931,7 @@ class _RankBadge extends StatelessWidget { Widget build(BuildContext context) { final isTop3 = rank <= 3; final medalColors = [ - const Color(0xFFF59E0B), + cs.warning, cs.onSurfaceVariant, const Color(0xFFCD7F32) ]; diff --git a/siro_admin/lib/views/admin/wallet/wallet.dart b/siro_admin/lib/views/admin/wallet/wallet.dart index dafb77eb..75d2a5f0 100644 --- a/siro_admin/lib/views/admin/wallet/wallet.dart +++ b/siro_admin/lib/views/admin/wallet/wallet.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../controller/admin/wallet_admin_controller.dart'; @@ -195,14 +196,14 @@ class Wallet extends StatelessWidget { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - const Color(0xFF10B981).withValues(alpha: 0.20), - const Color(0xFF10B981).withValues(alpha: 0.08), + cs.success.withValues(alpha: 0.20), + cs.success.withValues(alpha: 0.08), ], ), borderRadius: BorderRadius.circular(12), - border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.2)), + border: Border.all(color: cs.success.withValues(alpha: 0.2)), ), - child: Icon(Icons.person_rounded, color: const Color(0xFF10B981), size: 22), + child: Icon(Icons.person_rounded, color: cs.success, size: 22), ), const SizedBox(width: 14), Expanded( @@ -234,7 +235,7 @@ class Wallet extends StatelessWidget { Text( amount, style: TextStyle( - color: const Color(0xFF10B981), + color: cs.success, fontWeight: FontWeight.w800, fontSize: 18, ), @@ -242,7 +243,7 @@ class Wallet extends StatelessWidget { Text( 'ل.س', style: TextStyle( - color: const Color(0xFF10B981).withValues(alpha: 0.6), + color: cs.success.withValues(alpha: 0.6), fontSize: 10, fontWeight: FontWeight.w500, ), diff --git a/siro_admin/lib/views/invoice/invoice_list_page.dart b/siro_admin/lib/views/invoice/invoice_list_page.dart index 71c5e0ff..7dc66065 100644 --- a/siro_admin/lib/views/invoice/invoice_list_page.dart +++ b/siro_admin/lib/views/invoice/invoice_list_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constant/links.dart'; @@ -447,7 +448,7 @@ class _InvoiceListPageState extends State { Text( amount, style: TextStyle( - color: const Color(0xFF10B981), + color: cs.success, fontWeight: FontWeight.w800, fontSize: 18, ), @@ -455,7 +456,7 @@ class _InvoiceListPageState extends State { Text( "د.أ", style: TextStyle( - color: const Color(0xFF10B981).withValues(alpha: 0.6), + color: cs.success.withValues(alpha: 0.6), fontWeight: FontWeight.w500, fontSize: 10, ), diff --git a/siro_admin/lib/views/transit/org_list_page.dart b/siro_admin/lib/views/transit/org_list_page.dart index 70a75ead..8e7ddab7 100644 --- a/siro_admin/lib/views/transit/org_list_page.dart +++ b/siro_admin/lib/views/transit/org_list_page.dart @@ -1,3 +1,4 @@ +import 'package:siro_admin/constant/theme.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -121,7 +122,7 @@ class _TransitOrgListPageState extends State { Color statusColor; switch (org.contractStatus) { case 'active': - statusColor = const Color(0xFF10B981); + statusColor = cs.success; break; case 'trial': statusColor = cs.tertiary;