Update: 2026-08-02 22:51:18
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
// مربعات المدن الكبرى لتمثيل الدول (لتجنب حساب المضلعات المعقدة)
|
||||
|
||||
@@ -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 ساعة
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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`;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
# لم تكن في الجدول القديم. راجعها وقرّر: تُجدول أم تُحذف من الكود.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user