Update: 2026-08-02 22:51:18

This commit is contained in:
Hamza-Ayed
2026-08-02 22:51:18 +03:00
parent c9af364cbc
commit 2c962bf80a
39 changed files with 627 additions and 191 deletions
@@ -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"
+12 -5
View File
@@ -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
+8
View File
@@ -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";
// ==========================================
@@ -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. استخراج الركاب الخاملين في آخر ساعتين (الذين لم يقموا بأي طلب اليوم)
@@ -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";
// مربعات المدن الكبرى لتمثيل الدول (لتجنب حساب المضلعات المعقدة)
+8
View File
@@ -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) {
@@ -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 ساعة
+8
View File
@@ -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)
+7
View File
@@ -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
+42 -13
View File
@@ -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);
@@ -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`;
+3 -3
View File
@@ -30,18 +30,18 @@ WITH RECURSIVE date_series AS (
)
SELECT
date_series.date AS day,
COALESCE(SUM(ride.status = 'Finished'), 0) AS totalRides,
COALESCE(SUM(LOWER(ride.status) IN ('finished','completed')), 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
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'
AND LOWER(ride.status) IN ('finished','completed')
WHERE
date_series.date >= :start_date_where
AND date_series.date <= :end_date_where
+9
View File
@@ -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=<CHANGE_ME_BOT_SECRET>
# معرّفات الأجهزة المسموح لها (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
+92
View File
@@ -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
# لم تكن في الجدول القديم. راجعها وقرّر: تُجدول أم تُحذف من الكود.
+24
View File
@@ -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.
+191 -20
View File
@@ -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<AdminHomePage>
);
}
/// توزيع الرحلات على فترات اليوم (صباح / مساء / ليل).
/// الحدود مطابقة لما يحسبه 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<AdminHomePage>
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<AdminHomePage>
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<AdminHomePage>
),
),
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<AdminHomePage>
}
List<ActionCategory> _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<AdminHomePage>
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<AdminHomePage>
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<AdminHomePage>
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<AdminHomePage>
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<AdminHomePage>
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<AdminHomePage>
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<AdminHomePage>
() => 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<AdminHomePage>
ActionItem(
'إضافة خدمة عملاء',
Icons.support_agent_rounded,
const Color(0xFF3B82F6),
cs.info,
() => Get.to(() => const AddStaffPage(role: 'service')),
),
],
@@ -1297,25 +1468,25 @@ class _AdminHomePageState extends State<AdminHomePage>
'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',
@@ -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 = <String>{};
@@ -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;
}
}
@@ -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,
),
),
],
@@ -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;
}
}
@@ -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:
@@ -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) {
@@ -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(
@@ -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<SiroTrackerScreen>
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<SiroTrackerScreen>
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<SiroTrackerScreen>
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<SiroTrackerScreen>
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<SiroTrackerScreen>
setState(() => isLiveMode = true);
fetchData();
},
const Color(0xFF10B981),
cs.success,
cs,
),
),
@@ -646,7 +647,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
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<SiroTrackerScreen>
size: 14,
color: isLoading
? cs.tertiary
: const Color(0xFF10B981),
: cs.success,
),
],
),
@@ -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(
@@ -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),
),
),
),
@@ -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<ErrorListPage> {
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,
@@ -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,
),
@@ -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),
],
),
],
@@ -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,
),
),
],
@@ -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),
@@ -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();
@@ -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<RidesDashboardScreen>
'جارية',
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<RidesDashboardScreen>
'ملغاة',
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<RidesDashboardScreen>
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<RidesDashboardScreen>
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<RidesDashboardScreen>
_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<RidesDashboardScreen>
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<RidesDashboardScreen>
// 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<RideMapMonitorScreen> {
@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<RideMapMonitorScreen> {
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<RideMapMonitorScreen> {
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<RideMapMonitorScreen> {
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<RideMapMonitorScreen> {
title: 'السائق',
name: widget.ride.driverName,
phone: widget.ride.driverPhone,
color: const Color(0xFF3B82F6),
color: cs.info,
),
const SizedBox(height: 12),
// Passenger Info
+9 -8
View File
@@ -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(
@@ -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),
),
@@ -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<AnalyticsV2Controller>(
init: Get.isRegistered<AnalyticsV2Controller>()
? Get.find<AnalyticsV2Controller>()
: 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,
),
@@ -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)
];
@@ -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,
),
@@ -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<InvoiceListPage> {
Text(
amount,
style: TextStyle(
color: const Color(0xFF10B981),
color: cs.success,
fontWeight: FontWeight.w800,
fontSize: 18,
),
@@ -455,7 +456,7 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
Text(
"د.أ",
style: TextStyle(
color: const Color(0xFF10B981).withValues(alpha: 0.6),
color: cs.success.withValues(alpha: 0.6),
fontWeight: FontWeight.w500,
fontSize: 10,
),
@@ -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<TransitOrgListPage> {
Color statusColor;
switch (org.contractStatus) {
case 'active':
statusColor = const Color(0xFF10B981);
statusColor = cs.success;
break;
case 'trial':
statusColor = cs.tertiary;