Update: 2026-08-02 22:51:18
This commit is contained in:
@@ -29,7 +29,10 @@ class WorkerClient(private val context: Context) {
|
|||||||
// حتى يكون تبديله عند نسخ البوت لعلامة أخرى سطراً واحداً في gradle.
|
// حتى يكون تبديله عند نسخ البوت لعلامة أخرى سطراً واحداً في gradle.
|
||||||
// للتجريب المحلي: اضبط BACKEND_HOST على "http://10.0.2.2:8000" مع تقديم
|
// للتجريب المحلي: اضبط BACKEND_HOST على "http://10.0.2.2:8000" مع تقديم
|
||||||
// نفس المسار /backend/bot/ من جذر السيرفر المحلي.
|
// نفس المسار /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 {
|
private fun generateSignature(deviceId: String, ts: Long): String {
|
||||||
val message = "$deviceId$ts"
|
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 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 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,
|
-- يُدخل الرحلات الملغاة و timeout (مسافتها 0 أو غير مضبوطة) في المتوسط
|
||||||
(SELECT ROUND(MAX(distance),2) FROM ride) AS longest_distance,
|
-- فيهبط 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'،
|
-- ملاحظة: خط الرحلات الحالي يكتب 'completed' بينما القديم يكتب 'Finished'،
|
||||||
-- والاكتفاء بالقديم كان يُرجع NULL للأرباح وصفراً للرحلات المكتملة/الملغاة.
|
-- والاكتفاء بالقديم كان يُرجع NULL للأرباح وصفراً للرحلات المكتملة/الملغاة.
|
||||||
(SELECT SUM(price_for_driver) FROM ride WHERE LOWER(status) IN ('finished','completed')) AS total_driver_earnings,
|
(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,
|
(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 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
|
0 AS transfer_from_count
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ try {
|
|||||||
die("Connection failed: " . $e->getMessage() . "\n");
|
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";
|
echo "Starting Siro AI Engine v2 (Powered by Pricing Engine)...\n";
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ try {
|
|||||||
die("Database connection failed: " . $e->getMessage() . "\n");
|
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";
|
echo "Starting Auto Marketing Pusher Engine...\n";
|
||||||
|
|
||||||
// 1. استخراج الركاب الخاملين في آخر ساعتين (الذين لم يقموا بأي طلب اليوم)
|
// 1. استخراج الركاب الخاملين في آخر ساعتين (الذين لم يقموا بأي طلب اليوم)
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ try {
|
|||||||
die("Connection failed: " . $e->getMessage() . "\n");
|
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";
|
echo "Starting Heatmap Cache Generation (Redis)...\n";
|
||||||
|
|
||||||
// مربعات المدن الكبرى لتمثيل الدول (لتجنب حساب المضلعات المعقدة)
|
// مربعات المدن الكبرى لتمثيل الدول (لتجنب حساب المضلعات المعقدة)
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ try {
|
|||||||
die("Connection failed: " . $e->getMessage() . "\n");
|
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'];
|
$countries = ['SY', 'JO', 'EG', 'IQ'];
|
||||||
|
|
||||||
foreach ($countries as $country) {
|
foreach ($countries as $country) {
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ try {
|
|||||||
die("[ReEngagement] Connection failed: " . $e->getMessage() . "\n");
|
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";
|
echo "[ReEngagement] Starting Passenger Re-engagement Engine...\n";
|
||||||
|
|
||||||
$REDIS_TTL = 72 * 3600; // anti-spam: 72 ساعة
|
$REDIS_TTL = 72 * 3600; // anti-spam: 72 ساعة
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ try {
|
|||||||
die("[PredictiveDemand] Connection failed: " . $e->getMessage() . "\n");
|
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";
|
echo "[PredictiveDemand] Starting...\n";
|
||||||
|
|
||||||
$GRID_SIZE = 0.01; // حجم الخلية (~1.1km)
|
$GRID_SIZE = 0.01; // حجم الخلية (~1.1km)
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ try {
|
|||||||
die("Connection failed: " . $e->getMessage() . "\n");
|
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'];
|
$countries = ['SY' => 'Asia/Damascus', 'JO' => 'Asia/Amman', 'EG' => 'Africa/Cairo', 'IQ' => 'Asia/Baghdad'];
|
||||||
|
|
||||||
// Simulated Calendar of Events
|
// Simulated Calendar of Events
|
||||||
|
|||||||
+34
-5
@@ -23,7 +23,15 @@ if (!$SECRET_KEY) {
|
|||||||
echo json_encode(['status' => 'failure', 'message' => 'Server configuration error: BOT_SECRET_KEY not set in environment']);
|
echo json_encode(['status' => 'failure', 'message' => 'Server configuration error: BOT_SECRET_KEY not set in environment']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
// أجهزة البوت المسموح بها — تُضبط عبر 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'];
|
$ALLOWED_DEVICES = ['SHAM_CASH_BOT_01', 'PRICE_SCRAPER_BOT_01', '77a9528112ef7c2a'];
|
||||||
|
}
|
||||||
|
|
||||||
$method = $_SERVER['REQUEST_METHOD'];
|
$method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
|
||||||
@@ -43,9 +51,12 @@ if ($method === 'GET') {
|
|||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// GET: Fetch Pending Task for the Bot
|
// GET: Fetch Pending Task for the Bot
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
$device_id = filterRequest('device_id');
|
// ملاحظة: filterRequest() تقرأ من POST أو JSON body فقط ولا تلمس $_GET
|
||||||
$ts = filterRequest('ts');
|
// (backend/core/helpers.php) — والبوت يرسل بارامترات التوقيع في الـ query
|
||||||
$sig = filterRequest('sig');
|
// 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) {
|
if (!$device_id || !$ts || !$sig) {
|
||||||
jsonError("Missing security parameters");
|
jsonError("Missing security parameters");
|
||||||
@@ -64,6 +75,14 @@ if ($method === 'GET') {
|
|||||||
|
|
||||||
// Check Redis Queue for tasks
|
// Check Redis Queue for tasks
|
||||||
// Queue Name: queue:bot:tasks (Using Main Redis)
|
// 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');
|
$taskJson = $redis->rpop('queue:bot:tasks');
|
||||||
|
|
||||||
if ($taskJson) {
|
if ($taskJson) {
|
||||||
@@ -144,7 +163,12 @@ if ($method === 'GET') {
|
|||||||
}
|
}
|
||||||
// ------------------------------------------------------
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
// العملة تتبع الدولة بدل تثبيتها على JOD — البوت يعمل على أكثر من سوق
|
||||||
|
$currencyByCountry = ['JO' => 'JOD', 'SY' => 'SYP', 'EG' => 'EGP'];
|
||||||
|
$currency = $currencyByCountry[strtoupper($country_code)] ?? 'JOD';
|
||||||
|
|
||||||
// 1. Save to MySQL
|
// 1. Save to MySQL
|
||||||
|
try {
|
||||||
$stmt = $con->prepare("
|
$stmt = $con->prepare("
|
||||||
INSERT INTO scraped_competitor_prices
|
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)
|
(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)
|
||||||
@@ -152,10 +176,15 @@ if ($method === 'GET') {
|
|||||||
");
|
");
|
||||||
$start_loc = "Lat: $start_lat, Lng: $start_lng";
|
$start_loc = "Lat: $start_lat, Lng: $start_lng";
|
||||||
$end_loc = "Lat: $end_lat, Lng: $end_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]);
|
$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)
|
// 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 in Redis (Main) to be used by Pricing Engine
|
||||||
// Store recent 50 prices for the app
|
// Store recent 50 prices for the app
|
||||||
$redis->lpush("competitor:price_history:$app_name", $pricePerKm);
|
$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`;
|
||||||
@@ -30,18 +30,18 @@ WITH RECURSIVE date_series AS (
|
|||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
date_series.date AS day,
|
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
|
(SELECT COUNT(*) FROM ride
|
||||||
WHERE ride.created_at >= :start_date_total
|
WHERE ride.created_at >= :start_date_total
|
||||||
AND ride.created_at <= :end_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
|
FROM
|
||||||
date_series
|
date_series
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
ride ON DATE(ride.created_at) = date_series.date
|
ride ON DATE(ride.created_at) = date_series.date
|
||||||
AND ride.status = 'Finished'
|
AND LOWER(ride.status) IN ('finished','completed')
|
||||||
WHERE
|
WHERE
|
||||||
date_series.date >= :start_date_where
|
date_series.date >= :start_date_where
|
||||||
AND date_series.date <= :end_date_where
|
AND date_series.date <= :end_date_where
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ REDIS_MAIN_HOST=redis
|
|||||||
REDIS_MAIN_PORT=6379
|
REDIS_MAIN_PORT=6379
|
||||||
REDIS_MAIN_PASSWORD=
|
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) ---
|
# --- 4. Internal Sockets (Docker Network Names) ---
|
||||||
LOCATION_SERVER_URL=http://socket_driver:2021
|
LOCATION_SERVER_URL=http://socket_driver:2021
|
||||||
RIDE_SOCKET_URL=http://socket_passenger:3031
|
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;
|
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) ─────────────────────────────
|
// ─── Semantic Color Getters (light/dark adaptive) ─────────────────────────────
|
||||||
// These replace direct AppColor.* usage in views. They automatically adapt
|
// These replace direct AppColor.* usage in views. They automatically adapt
|
||||||
// based on the current theme brightness.
|
// based on the current theme brightness.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
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(
|
Widget _buildChartsSection(
|
||||||
dynamic data, DashboardController controller, ColorScheme cs) {
|
dynamic data, DashboardController controller, ColorScheme cs) {
|
||||||
final completed = (data['completed_rides'] ?? 0).toDouble();
|
final completed = (data['completed_rides'] ?? 0).toDouble();
|
||||||
@@ -520,6 +682,12 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
final driverCount = (data['countDriver'] ?? 0).toDouble();
|
final driverCount = (data['countDriver'] ?? 0).toDouble();
|
||||||
final monthRides = (data['countRideThisMonth'] ?? 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(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -677,7 +845,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
barRods: [
|
barRods: [
|
||||||
BarChartRodData(
|
BarChartRodData(
|
||||||
toY: speed,
|
toY: speed,
|
||||||
color: const Color(0xFFF59E0B),
|
color: cs.warning,
|
||||||
width: 28,
|
width: 28,
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
|
||||||
),
|
),
|
||||||
@@ -828,6 +996,8 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
_buildPeakHoursCard(morningRides, eveningRides, nightRides, cs),
|
||||||
|
const SizedBox(height: 12),
|
||||||
AnimationConfiguration.staggeredList(
|
AnimationConfiguration.staggeredList(
|
||||||
position: 2,
|
position: 2,
|
||||||
duration: const Duration(milliseconds: 450),
|
duration: const Duration(milliseconds: 450),
|
||||||
@@ -1139,15 +1309,16 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<ActionCategory> _getAllActionCategories() {
|
List<ActionCategory> _getAllActionCategories() {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
return [
|
return [
|
||||||
ActionCategory(
|
ActionCategory(
|
||||||
title: 'المستخدمين',
|
title: 'المستخدمين',
|
||||||
items: [
|
items: [
|
||||||
ActionItem('الركاب', Icons.people_outline_rounded, const Color(0xFF3B82F6),
|
ActionItem('الركاب', Icons.people_outline_rounded, cs.info,
|
||||||
() => Get.to(() => Passengrs())),
|
() => Get.to(() => Passengrs())),
|
||||||
ActionItem('السائقون', Icons.drive_eta_rounded, const Color(0xFFF59E0B),
|
ActionItem('السائقون', Icons.drive_eta_rounded, cs.warning,
|
||||||
() => Get.to(() => CaptainsPage())),
|
() => Get.to(() => CaptainsPage())),
|
||||||
ActionItem('المراقب', Icons.track_changes_rounded, const Color(0xFFEF4444),
|
ActionItem('المراقب', Icons.track_changes_rounded, cs.danger,
|
||||||
() => Get.to(() => SiroTrackerScreen())),
|
() => Get.to(() => SiroTrackerScreen())),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1163,11 +1334,11 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
items: [
|
items: [
|
||||||
ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, const Color(0xFF6366F1),
|
ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, const Color(0xFF6366F1),
|
||||||
() => Get.toNamed('/promo')),
|
() => Get.toNamed('/promo')),
|
||||||
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, const Color(0xFFF59E0B),
|
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, cs.warning,
|
||||||
() => Get.toNamed('/kazan')),
|
() => Get.toNamed('/kazan')),
|
||||||
ActionItem('الشكاوى', Icons.report_problem_rounded, const Color(0xFFEF4444),
|
ActionItem('الشكاوى', Icons.report_problem_rounded, cs.danger,
|
||||||
() => Get.toNamed('/complaints')),
|
() => Get.toNamed('/complaints')),
|
||||||
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, const Color(0xFF3B82F6),
|
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, cs.info,
|
||||||
() => Get.toNamed('/driver-docs')),
|
() => Get.toNamed('/driver-docs')),
|
||||||
ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA),
|
ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA),
|
||||||
() => Get.toNamed('/social-intelligence')),
|
() => Get.toNamed('/social-intelligence')),
|
||||||
@@ -1188,7 +1359,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
await Get.put(StaticController()).getAll();
|
await Get.put(StaticController()).getAll();
|
||||||
Get.to(() => const StaticDash());
|
Get.to(() => const StaticDash());
|
||||||
}),
|
}),
|
||||||
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, const Color(0xFF3B82F6),
|
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, cs.info,
|
||||||
() => Get.to(() => const AdvancedAnalyticsPage())),
|
() => Get.to(() => const AdvancedAnalyticsPage())),
|
||||||
ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded,
|
ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded,
|
||||||
const Color(0xFF00CEC9),
|
const Color(0xFF00CEC9),
|
||||||
@@ -1198,7 +1369,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
ActionCategory(
|
ActionCategory(
|
||||||
title: 'الجودة والدعم',
|
title: 'الجودة والدعم',
|
||||||
items: [
|
items: [
|
||||||
ActionItem('القائمة السوداء', Icons.block_flipped, const Color(0xFFEF4444),
|
ActionItem('القائمة السوداء', Icons.block_flipped, cs.danger,
|
||||||
() => Get.to(() => const BlacklistPage())),
|
() => Get.to(() => const BlacklistPage())),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1210,10 +1381,10 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
const Color(0xFF6366F1), () => Get.to(() => const FinancialV2Page())),
|
const Color(0xFF6366F1), () => Get.to(() => const FinancialV2Page())),
|
||||||
ActionItem('المحفظة', Icons.account_balance_wallet_rounded, const Color(0xFF6366F1),
|
ActionItem('المحفظة', Icons.account_balance_wallet_rounded, const Color(0xFF6366F1),
|
||||||
() => Get.to(() => Wallet())),
|
() => Get.to(() => Wallet())),
|
||||||
ActionItem('هدية 300', Icons.card_giftcard_rounded, const Color(0xFFF59E0B),
|
ActionItem('هدية 300', Icons.card_giftcard_rounded, cs.warning,
|
||||||
() => Get.to(() => DriverGiftCheckPage())),
|
() => Get.to(() => DriverGiftCheckPage())),
|
||||||
ActionItem('الفواتير', Icons.receipt_long_rounded,
|
ActionItem('الفواتير', Icons.receipt_long_rounded,
|
||||||
const Color(0xFF10B981), () => Get.to(() => InvoiceListPage())),
|
cs.success, () => Get.to(() => InvoiceListPage())),
|
||||||
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFF9CA3AF),
|
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFF9CA3AF),
|
||||||
() => Get.to(() => EmployeePage())),
|
() => Get.to(() => EmployeePage())),
|
||||||
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, const Color(0xFF6366F1),
|
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, const Color(0xFF6366F1),
|
||||||
@@ -1225,11 +1396,11 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
title: 'النظام والتواصل',
|
title: 'النظام والتواصل',
|
||||||
items: [
|
items: [
|
||||||
ActionItem('سجل العمليات', Icons.admin_panel_settings_rounded,
|
ActionItem('سجل العمليات', Icons.admin_panel_settings_rounded,
|
||||||
const Color(0xFFEF4444), () => Get.to(() => const AuditLogsPage())),
|
cs.danger, () => Get.to(() => const AuditLogsPage())),
|
||||||
ActionItem('واتساب جماعي', Icons.message_rounded,
|
ActionItem('واتساب جماعي', Icons.message_rounded,
|
||||||
const Color(0xFF25D366), () => _showWhatsAppDialog(context)),
|
const Color(0xFF25D366), () => _showWhatsAppDialog(context)),
|
||||||
ActionItem('أتمتة التسويق', Icons.campaign_rounded,
|
ActionItem('أتمتة التسويق', Icons.campaign_rounded,
|
||||||
const Color(0xFF10B981), () => Get.toNamed('/marketing')),
|
cs.success, () => Get.toNamed('/marketing')),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
'إشعار سائقين',
|
'إشعار سائقين',
|
||||||
Icons.notifications_active_rounded,
|
Icons.notifications_active_rounded,
|
||||||
@@ -1242,7 +1413,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
const Color(0xFFF06292),
|
const Color(0xFFF06292),
|
||||||
() => Get.put(NotificationController())
|
() => Get.put(NotificationController())
|
||||||
.sendNotificationPassengers()),
|
.sendNotificationPassengers()),
|
||||||
ActionItem('تسجيل سائق', Icons.person_add_rounded, const Color(0xFF3B82F6),
|
ActionItem('تسجيل سائق', Icons.person_add_rounded, cs.info,
|
||||||
() => Get.to(() => DriversPendingPage())),
|
() => Get.to(() => DriversPendingPage())),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
'تحديث التطبيق',
|
'تحديث التطبيق',
|
||||||
@@ -1251,7 +1422,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
() => Get.to(() => PackageUpdateScreen())),
|
() => Get.to(() => PackageUpdateScreen())),
|
||||||
ActionItem('مراقب السيرفر', Icons.dns_rounded, const Color(0xFF6366F1),
|
ActionItem('مراقب السيرفر', Icons.dns_rounded, const Color(0xFF6366F1),
|
||||||
() => Get.to(() => ServerMonitorPage())),
|
() => Get.to(() => ServerMonitorPage())),
|
||||||
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, const Color(0xFFEF4444),
|
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, cs.danger,
|
||||||
() => Get.to(() => ErrorListPage())),
|
() => Get.to(() => ErrorListPage())),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
'أداة التشفير',
|
'أداة التشفير',
|
||||||
@@ -1275,7 +1446,7 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
ActionItem(
|
ActionItem(
|
||||||
'إضافة خدمة عملاء',
|
'إضافة خدمة عملاء',
|
||||||
Icons.support_agent_rounded,
|
Icons.support_agent_rounded,
|
||||||
const Color(0xFF3B82F6),
|
cs.info,
|
||||||
() => Get.to(() => const AddStaffPage(role: 'service')),
|
() => Get.to(() => const AddStaffPage(role: 'service')),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1297,25 +1468,25 @@ class _AdminHomePageState extends State<AdminHomePage>
|
|||||||
'title': 'مكتملة',
|
'title': 'مكتملة',
|
||||||
'value': data['completed_rides'],
|
'value': data['completed_rides'],
|
||||||
'icon': Icons.check_circle_rounded,
|
'icon': Icons.check_circle_rounded,
|
||||||
'color': const Color(0xFF10B981),
|
'color': cs.success,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'ملغاة',
|
'title': 'ملغاة',
|
||||||
'value': data['cancelled_rides'],
|
'value': data['cancelled_rides'],
|
||||||
'icon': Icons.cancel_rounded,
|
'icon': Icons.cancel_rounded,
|
||||||
'color': const Color(0xFFEF4444),
|
'color': cs.danger,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'مدفوعات',
|
'title': 'مدفوعات',
|
||||||
'value': _formatCurrency(data['payments']),
|
'value': _formatCurrency(data['payments']),
|
||||||
'icon': Icons.attach_money_rounded,
|
'icon': Icons.attach_money_rounded,
|
||||||
'color': const Color(0xFFF59E0B),
|
'color': cs.warning,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Comfort',
|
'title': 'Comfort',
|
||||||
'value': data['comfort'],
|
'value': data['comfort'],
|
||||||
'icon': Icons.chair_rounded,
|
'icon': Icons.chair_rounded,
|
||||||
'color': const Color(0xFF10B981),
|
'color': cs.success,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Speed',
|
'title': 'Speed',
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
@@ -342,7 +343,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
label: 'إيرادات اليوم',
|
label: 'إيرادات اليوم',
|
||||||
value: _fmt(revToday),
|
value: _fmt(revToday),
|
||||||
sub: '${revChange >= 0 ? '+' : ''}${revChange.toStringAsFixed(1)}% عن الأمس',
|
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,
|
icon: Icons.attach_money_rounded,
|
||||||
),
|
),
|
||||||
_StatCard(
|
_StatCard(
|
||||||
@@ -380,7 +381,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
(v) => setState(() => showGap = v)),
|
(v) => setState(() => showGap = v)),
|
||||||
_LayerChip('كثافة الطلب', showHeatmap, Colors.orange,
|
_LayerChip('كثافة الطلب', showHeatmap, Colors.orange,
|
||||||
(v) => setState(() => showHeatmap = v)),
|
(v) => setState(() => showHeatmap = v)),
|
||||||
_LayerChip('مواقع الكباتن', showSupply, const Color(0xFF10B981),
|
_LayerChip('مواقع الكباتن', showSupply, cs.success,
|
||||||
(v) => setState(() => showSupply = v)),
|
(v) => setState(() => showSupply = v)),
|
||||||
_LayerChip('أسعار المنافسين', showPricing, cs.tertiary,
|
_LayerChip('أسعار المنافسين', showPricing, cs.tertiary,
|
||||||
(v) => setState(() => showPricing = v)),
|
(v) => setState(() => showPricing = v)),
|
||||||
@@ -409,7 +410,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
point: LatLng(lat, lng),
|
point: LatLng(lat, lng),
|
||||||
color: gap > 0
|
color: gap > 0
|
||||||
? Colors.redAccent.withValues(alpha: 0.3 + intensity * 0.5)
|
? Colors.redAccent.withValues(alpha: 0.3 + intensity * 0.5)
|
||||||
: const Color(0xFF10B981).withValues(alpha: 0.4),
|
: cs.success.withValues(alpha: 0.4),
|
||||||
borderStrokeWidth: 0,
|
borderStrokeWidth: 0,
|
||||||
radius: 12 + intensity * 8,
|
radius: 12 + intensity * 8,
|
||||||
));
|
));
|
||||||
@@ -442,7 +443,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (lat == 0 || lng == 0) continue;
|
if (lat == 0 || lng == 0) continue;
|
||||||
markers.add(CircleMarker(
|
markers.add(CircleMarker(
|
||||||
point: LatLng(lat, lng),
|
point: LatLng(lat, lng),
|
||||||
color: const Color(0xFF10B981).withValues(alpha: 0.5),
|
color: cs.success.withValues(alpha: 0.5),
|
||||||
borderStrokeWidth: 1,
|
borderStrokeWidth: 1,
|
||||||
borderColor: Colors.white.withValues(alpha: 0.4),
|
borderColor: Colors.white.withValues(alpha: 0.4),
|
||||||
radius: 6 + supply.clamp(0, 10).toDouble(),
|
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();
|
if (statuses.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
final colors = {
|
final colors = {
|
||||||
'Finished': const Color(0xFF10B981),
|
'Finished': cs.success,
|
||||||
'wait': cs.tertiary,
|
'wait': cs.tertiary,
|
||||||
'started': cs.tertiary,
|
'started': cs.tertiary,
|
||||||
'arrived': cs.primary,
|
'arrived': cs.primary,
|
||||||
@@ -701,7 +702,7 @@ class _MarketTab extends StatelessWidget {
|
|||||||
cs.error,
|
cs.error,
|
||||||
cs.tertiary,
|
cs.tertiary,
|
||||||
cs.tertiary,
|
cs.tertiary,
|
||||||
const Color(0xFF10B981),
|
cs.success,
|
||||||
cs.primary,
|
cs.primary,
|
||||||
];
|
];
|
||||||
final hours = <String>{};
|
final hours = <String>{};
|
||||||
@@ -940,7 +941,7 @@ class _FleetTab extends StatelessWidget {
|
|||||||
cs.tertiary,
|
cs.tertiary,
|
||||||
cs.tertiary,
|
cs.tertiary,
|
||||||
cs.primary,
|
cs.primary,
|
||||||
const Color(0xFF10B981),
|
cs.success,
|
||||||
cs.onSurfaceVariant,
|
cs.onSurfaceVariant,
|
||||||
const Color(0xFFfd79a8),
|
const Color(0xFFfd79a8),
|
||||||
];
|
];
|
||||||
@@ -994,7 +995,7 @@ class _FleetTab extends StatelessWidget {
|
|||||||
return FlSpot(
|
return FlSpot(
|
||||||
e.key.toDouble(), _toDouble(e.value['company_profit']));
|
e.key.toDouble(), _toDouble(e.value['company_profit']));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
barWidth: 2,
|
barWidth: 2,
|
||||||
dotData: const FlDotData(show: false),
|
dotData: const FlDotData(show: false),
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
@@ -1109,7 +1110,7 @@ class _GrowthTab extends StatelessWidget {
|
|||||||
final pct = ((last - prev) / prev * 100).toStringAsFixed(1);
|
final pct = ((last - prev) / prev * 100).toStringAsFixed(1);
|
||||||
weekChange = '${double.parse(pct) >= 0 ? '+' : ''}$pct% عن الأسبوع السابق';
|
weekChange = '${double.parse(pct) >= 0 ? '+' : ''}$pct% عن الأسبوع السابق';
|
||||||
weekColor =
|
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:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/controller/drivers/driver_not_active_controller.dart';
|
import 'package:siro_admin/controller/drivers/driver_not_active_controller.dart';
|
||||||
@@ -75,13 +76,13 @@ Expanded(
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
|
color: cs.warning.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
|
color: cs.warning.withValues(alpha: 0.25)),
|
||||||
),
|
),
|
||||||
child: Icon(Icons.hourglass_top_rounded,
|
child: Icon(Icons.hourglass_top_rounded,
|
||||||
color: const Color(0xFFF59E0B), size: 18),
|
color: cs.warning, size: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
@@ -171,19 +172,19 @@ Expanded(
|
|||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [
|
colors: [
|
||||||
const Color(0xFFF59E0B).withValues(alpha: 0.20),
|
cs.warning.withValues(alpha: 0.20),
|
||||||
const Color(0xFFF59E0B).withValues(alpha: 0.08),
|
cs.warning.withValues(alpha: 0.08),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.2)),
|
color: cs.warning.withValues(alpha: 0.2)),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'D',
|
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'D',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xFFF59E0B),
|
color: cs.warning,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
),
|
),
|
||||||
@@ -210,7 +211,7 @@ Expanded(
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 6, vertical: 2),
|
horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFF59E0B)
|
color: cs.warning
|
||||||
.withValues(alpha: 0.1),
|
.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
@@ -219,14 +220,14 @@ Expanded(
|
|||||||
children: [
|
children: [
|
||||||
Icon(Icons.hourglass_top_rounded,
|
Icon(Icons.hourglass_top_rounded,
|
||||||
size: 11,
|
size: 11,
|
||||||
color: const Color(0xFFF59E0B)),
|
color: cs.warning),
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
const Text(
|
Text(
|
||||||
'معلق',
|
'معلق',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w700,
|
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:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -249,7 +250,7 @@ class ComplaintListPage extends StatelessWidget {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Open': return cs.error;
|
case 'Open': return cs.error;
|
||||||
case 'In Progress': return cs.tertiary;
|
case 'In Progress': return cs.tertiary;
|
||||||
case 'Resolved': return const Color(0xFF10B981);
|
case 'Resolved': return cs.success;
|
||||||
default: return cs.onSurfaceVariant;
|
default: return cs.onSurfaceVariant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart';
|
import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart';
|
||||||
@@ -85,13 +86,13 @@ class DashboardV2Widget extends StatelessWidget {
|
|||||||
'title': 'سائقون أونلاين',
|
'title': 'سائقون أونلاين',
|
||||||
'value': data['online_drivers']?.toString() ?? '0',
|
'value': data['online_drivers']?.toString() ?? '0',
|
||||||
'icon': Icons.wifi_tethering,
|
'icon': Icons.wifi_tethering,
|
||||||
'color': const Color(0xFF10B981),
|
'color': cs.success,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'إيرادات اليوم',
|
'title': 'إيرادات اليوم',
|
||||||
'value': '${data['revenue_today'] ?? 0}',
|
'value': '${data['revenue_today'] ?? 0}',
|
||||||
'icon': Icons.monetization_on_rounded,
|
'icon': Icons.monetization_on_rounded,
|
||||||
'color': const Color(0xFFF59E0B),
|
'color': cs.warning,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'إيرادات الأمس',
|
'title': 'إيرادات الأمس',
|
||||||
@@ -225,7 +226,7 @@ class DashboardV2Widget extends StatelessWidget {
|
|||||||
case 'high':
|
case 'high':
|
||||||
return cs.error;
|
return cs.error;
|
||||||
case 'medium':
|
case 'medium':
|
||||||
return const Color(0xFFF59E0B);
|
return cs.warning;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
return const Color(0xFFF97316);
|
return const Color(0xFFF97316);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -150,7 +151,7 @@ class DriverDocsReviewPage extends StatelessWidget {
|
|||||||
MyElevatedButton(
|
MyElevatedButton(
|
||||||
title: 'اعتماد وتفعيل الحساب',
|
title: 'اعتماد وتفعيل الحساب',
|
||||||
icon: Icons.check_circle_rounded,
|
icon: Icons.check_circle_rounded,
|
||||||
kolor: const Color(0xFF10B981),
|
kolor: cs.success,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
bool success = await controller.approveDriver(id);
|
bool success = await controller.approveDriver(id);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/controller/functions/crud.dart';
|
import 'package:siro_admin/controller/functions/crud.dart';
|
||||||
@@ -139,13 +140,13 @@ class DriverGiftCheckPage extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
|
color: cs.warning.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
|
color: cs.warning.withValues(alpha: 0.25)),
|
||||||
),
|
),
|
||||||
child: Icon(Icons.card_giftcard_rounded,
|
child: Icon(Icons.card_giftcard_rounded,
|
||||||
color: const Color(0xFFF59E0B), size: 18),
|
color: cs.warning, size: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
@@ -184,16 +185,16 @@ class DriverGiftCheckPage extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
const Color(0xFFF59E0B).withValues(alpha: 0.20),
|
cs.warning.withValues(alpha: 0.20),
|
||||||
const Color(0xFFF59E0B).withValues(alpha: 0.08),
|
cs.warning.withValues(alpha: 0.08),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(
|
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,
|
child: Icon(Icons.card_giftcard_rounded,
|
||||||
size: 40, color: Color(0xFFF59E0B)),
|
size: 40, color: cs.warning),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
@@ -244,7 +245,7 @@ class DriverGiftCheckPage extends StatelessWidget {
|
|||||||
? null
|
? null
|
||||||
: () => controller.processDriverGift(),
|
: () => controller.processDriverGift(),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFF59E0B),
|
backgroundColor: cs.warning,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
disabledBackgroundColor: cs.outline,
|
disabledBackgroundColor: cs.outline,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
@@ -210,7 +211,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: isLiveMode
|
colors: isLiveMode
|
||||||
? [const Color(0xFF10B981), const Color(0xFF10B981)]
|
? [cs.success, cs.success]
|
||||||
: [cs.tertiary, cs.tertiary],
|
: [cs.tertiary, cs.tertiary],
|
||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
@@ -218,7 +219,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: (isLiveMode
|
color: (isLiveMode
|
||||||
? const Color(0xFF10B981)
|
? cs.success
|
||||||
: cs.tertiary)
|
: cs.tertiary)
|
||||||
.withValues(alpha: 0.5),
|
.withValues(alpha: 0.5),
|
||||||
blurRadius: 12,
|
blurRadius: 12,
|
||||||
@@ -281,7 +282,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: isLiveMode
|
colors: isLiveMode
|
||||||
? [const Color(0xFF10B981), const Color(0xFF10B981)]
|
? [cs.success, cs.success]
|
||||||
: [cs.tertiary, cs.tertiary],
|
: [cs.tertiary, cs.tertiary],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -417,7 +418,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildStatItem("✓ مكتملة", completed, const Color(0xFF10B981), cs),
|
_buildStatItem("✓ مكتملة", completed, cs.success, cs),
|
||||||
Container(
|
Container(
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 40,
|
height: 40,
|
||||||
@@ -635,7 +636,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
setState(() => isLiveMode = true);
|
setState(() => isLiveMode = true);
|
||||||
fetchData();
|
fetchData();
|
||||||
},
|
},
|
||||||
const Color(0xFF10B981),
|
cs.success,
|
||||||
cs,
|
cs,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -646,7 +647,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
icon: Icons.live_tv,
|
icon: Icons.live_tv,
|
||||||
label: "نشط الآن (مباشر)",
|
label: "نشط الآن (مباشر)",
|
||||||
value: liveCount.toString(),
|
value: liveCount.toString(),
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
cs: cs,
|
cs: cs,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -685,7 +686,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
size: 14,
|
size: 14,
|
||||||
color: isLoading
|
color: isLoading
|
||||||
? cs.tertiary
|
? cs.tertiary
|
||||||
: const Color(0xFF10B981),
|
: cs.success,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -428,14 +429,14 @@ class RideMonitorScreen extends StatelessWidget {
|
|||||||
point: controller.startPoint.value!,
|
point: controller.startPoint.value!,
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
child: _buildPointMarker(const Color(0xFF10B981), cs),
|
child: _buildPointMarker(cs.success, cs),
|
||||||
),
|
),
|
||||||
if (controller.endPoint.value != null)
|
if (controller.endPoint.value != null)
|
||||||
Marker(
|
Marker(
|
||||||
point: controller.endPoint.value!,
|
point: controller.endPoint.value!,
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
child: _buildPointMarker(const Color(0xFFEF4444), cs),
|
child: _buildPointMarker(cs.danger, cs),
|
||||||
),
|
),
|
||||||
if (controller.driverLocation.value != null)
|
if (controller.driverLocation.value != null)
|
||||||
Marker(
|
Marker(
|
||||||
@@ -666,8 +667,8 @@ class RideMonitorScreen extends StatelessWidget {
|
|||||||
color: controller.rideStatus.value
|
color: controller.rideStatus.value
|
||||||
.toLowerCase() ==
|
.toLowerCase() ==
|
||||||
'begin'
|
'begin'
|
||||||
? const Color(0xFF10B981)
|
? cs.success
|
||||||
: const Color(0xFFF59E0B),
|
: cs.warning,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
@@ -697,7 +698,7 @@ class RideMonitorScreen extends StatelessWidget {
|
|||||||
_buildModernInfoBadge(
|
_buildModernInfoBadge(
|
||||||
Icons.speed_rounded,
|
Icons.speed_rounded,
|
||||||
"${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س",
|
"${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س",
|
||||||
const Color(0xFF3B82F6),
|
cs.info,
|
||||||
cs,
|
cs,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/constant/links.dart';
|
import 'package:siro_admin/constant/links.dart';
|
||||||
@@ -131,7 +132,7 @@ class _EmployeeCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool isExcellent = employee['status'].toString().contains('ممتاز');
|
bool isExcellent = employee['status'].toString().contains('ممتاز');
|
||||||
Color statusColor = isExcellent ? const Color(0xFF10B981) : Colors.amber;
|
Color statusColor = isExcellent ? cs.success : Colors.amber;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
@@ -234,7 +235,7 @@ class _EmployeeCard extends StatelessWidget {
|
|||||||
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Material(
|
Material(
|
||||||
color: const Color(0xFF10B981).withValues(alpha: 0.1),
|
color: cs.success.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
@@ -242,8 +243,8 @@ class _EmployeeCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
child: const Icon(Icons.call,
|
child: Icon(Icons.call,
|
||||||
color: Color(0xFF10B981), size: 24),
|
color: cs.success, size: 24),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/constant/links.dart';
|
import 'package:siro_admin/constant/links.dart';
|
||||||
@@ -224,10 +225,10 @@ class _ErrorListPageState extends State<ErrorListPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
Icon(
|
||||||
Icons.check_circle_outline,
|
Icons.check_circle_outline,
|
||||||
size: 64,
|
size: 64,
|
||||||
color: Color(0xFF10B981),
|
color: cs.success,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
@@ -418,13 +419,13 @@ class _ErrorTile extends StatelessWidget {
|
|||||||
item.userType.toLowerCase().contains('سائق');
|
item.userType.toLowerCase().contains('سائق');
|
||||||
|
|
||||||
final userTypeColor =
|
final userTypeColor =
|
||||||
isDriver ? const Color(0xFF10B981) : const Color(0xFFF59E0B);
|
isDriver ? cs.success : cs.warning;
|
||||||
final userTypeIcon = isDriver ? Icons.directions_car : Icons.person;
|
final userTypeIcon = isDriver ? Icons.directions_car : Icons.person;
|
||||||
final userTypeLabel = isDriver ? "سائق" : "راكب";
|
final userTypeLabel = isDriver ? "سائق" : "راكب";
|
||||||
|
|
||||||
final userTypeBgColor = isDriver
|
final userTypeBgColor = isDriver
|
||||||
? const Color(0xFF10B981).withValues(alpha: 0.1)
|
? cs.success.withValues(alpha: 0.1)
|
||||||
: const Color(0xFFF59E0B).withValues(alpha: 0.1);
|
: cs.warning.withValues(alpha: 0.1);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
@@ -453,7 +454,7 @@ class _ErrorTile extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
const Color(0xFFEF4444),
|
cs.danger,
|
||||||
cs.error,
|
cs.error,
|
||||||
],
|
],
|
||||||
begin: Alignment.centerLeft,
|
begin: Alignment.centerLeft,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/controller/admin/financial_v2_controller.dart';
|
import 'package:siro_admin/controller/admin/financial_v2_controller.dart';
|
||||||
@@ -120,7 +121,7 @@ class FinancialV2Page extends StatelessWidget {
|
|||||||
'إجمالي الإيرادات',
|
'إجمالي الإيرادات',
|
||||||
'${stats['total_revenue'] ?? 0}',
|
'${stats['total_revenue'] ?? 0}',
|
||||||
Icons.payments_rounded,
|
Icons.payments_rounded,
|
||||||
const Color(0xFF10B981),
|
cs.success,
|
||||||
cs,
|
cs,
|
||||||
isSmall: true,
|
isSmall: true,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -211,7 +212,7 @@ class MarketingPage extends StatelessWidget {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildStatItem('سعرنا', '${anomaly['our_price']}', const Color(0xFF10B981), cs),
|
_buildStatItem('سعرنا', '${anomaly['our_price']}', cs.success, cs),
|
||||||
const SizedBox(width: 24),
|
const SizedBox(width: 24),
|
||||||
_buildStatItem('سعر المنافس', '${anomaly['competitor_price']}', cs.error, cs),
|
_buildStatItem('سعر المنافس', '${anomaly['competitor_price']}', cs.error, cs),
|
||||||
const SizedBox(width: 24),
|
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)),
|
child: Text(region['name'] as String, style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
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),
|
const SizedBox(height: 6),
|
||||||
@@ -449,8 +450,8 @@ class MarketingPage extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: ((region['weight'] as double) + 1) / 2,
|
value: ((region['weight'] as double) + 1) / 2,
|
||||||
backgroundColor: const Color(0xFF10B981).withValues(alpha: 0.3),
|
backgroundColor: cs.success.withValues(alpha: 0.3),
|
||||||
color: (region['pci'] as double) < 1 ? const Color(0xFF10B981) : cs.error,
|
color: (region['pci'] as double) < 1 ? cs.success : cs.error,
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -518,7 +519,7 @@ class MarketingPage extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
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),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -544,7 +545,7 @@ class MarketingPage extends StatelessWidget {
|
|||||||
c.simulatorRecommendationMessage ?? '',
|
c.simulatorRecommendationMessage ?? '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
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';
|
final channel = log['channel']?.toString().toUpperCase() ?? 'SMS';
|
||||||
|
|
||||||
Color channelColor = cs.tertiary;
|
Color channelColor = cs.tertiary;
|
||||||
if (channel == 'SMS') channelColor = const Color(0xFF10B981);
|
if (channel == 'SMS') channelColor = cs.success;
|
||||||
if (channel == 'WHATSAPP') channelColor = const Color(0xFF25D366);
|
if (channel == 'WHATSAPP') channelColor = const Color(0xFF25D366);
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
@@ -689,7 +690,7 @@ class MarketingPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_buildTelemetryItem('طلبات الـ API', '${c.apiRequestsCount}', cs.onSurface, cs),
|
_buildTelemetryItem('طلبات الـ API', '${c.apiRequestsCount}', cs.onSurface, cs),
|
||||||
_buildTelemetryItem('الرموز (Tokens)', '${c.totalTokensUsed}', 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:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -205,7 +206,7 @@ class Passengrs extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 13)),
|
fontSize: 13)),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFF59E0B),
|
backgroundColor: cs.warning,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(14)),
|
borderRadius: BorderRadius.circular(14)),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@@ -391,23 +392,23 @@ class Passengrs extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 6, vertical: 2),
|
horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFF59E0B)
|
color: cs.warning
|
||||||
.withValues(alpha: 0.1),
|
.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.star_rounded,
|
Icon(Icons.star_rounded,
|
||||||
size: 12, color: Color(0xFFF59E0B)),
|
size: 12, color: cs.warning),
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
Text(
|
Text(
|
||||||
user['ratingPassenger']?.toString() ??
|
user['ratingPassenger']?.toString() ??
|
||||||
'0.0',
|
'0.0',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w700,
|
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:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -112,9 +113,9 @@ class PromoManagementPage extends StatelessWidget {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.money_rounded, size: 14, color: const Color(0xFF10B981)),
|
Icon(Icons.money_rounded, size: 14, color: cs.success),
|
||||||
const SizedBox(width: 4),
|
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),
|
const SizedBox(width: 12),
|
||||||
Icon(Icons.person_rounded, size: 14, color: cs.tertiary),
|
Icon(Icons.person_rounded, size: 14, color: cs.tertiary),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../controller/admin/quality_controller.dart';
|
import '../../../controller/admin/quality_controller.dart';
|
||||||
@@ -304,12 +305,12 @@ class BlacklistPage extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFF10B981).withValues(alpha: 0.1),
|
color: cs.success.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(10),
|
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,
|
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: "نعم، فك الحظر",
|
textConfirm: "نعم، فك الحظر",
|
||||||
textCancel: "تراجع",
|
textCancel: "تراجع",
|
||||||
confirmTextColor: Colors.white,
|
confirmTextColor: Colors.white,
|
||||||
buttonColor: const Color(0xFF10B981),
|
buttonColor: cs.success,
|
||||||
cancelTextColor: cs.onSurfaceVariant,
|
cancelTextColor: cs.onSurfaceVariant,
|
||||||
onConfirm: () {
|
onConfirm: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -418,9 +419,9 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
'جارية',
|
'جارية',
|
||||||
controller.beginCount.toString(),
|
controller.beginCount.toString(),
|
||||||
Icons.directions_car_rounded,
|
Icons.directions_car_rounded,
|
||||||
const Color(0xFF10B981), cs)),
|
cs.success, cs)),
|
||||||
Obx(() => _buildStatCard('جديدة', controller.newCount.toString(),
|
Obx(() => _buildStatCard('جديدة', controller.newCount.toString(),
|
||||||
Icons.new_releases_rounded, const Color(0xFF3B82F6), cs)),
|
Icons.new_releases_rounded, cs.info, cs)),
|
||||||
Obx(() => _buildStatCard(
|
Obx(() => _buildStatCard(
|
||||||
'مكتملة',
|
'مكتملة',
|
||||||
controller.completedCount.toString(),
|
controller.completedCount.toString(),
|
||||||
@@ -430,12 +431,12 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
'ملغاة',
|
'ملغاة',
|
||||||
controller.canceledCount.toString(),
|
controller.canceledCount.toString(),
|
||||||
Icons.cancel_rounded,
|
Icons.cancel_rounded,
|
||||||
const Color(0xFFEF4444), cs)),
|
cs.danger, cs)),
|
||||||
Obx(() => _buildStatCard(
|
Obx(() => _buildStatCard(
|
||||||
'الإيرادات',
|
'الإيرادات',
|
||||||
controller.totalRevenue.value.toStringAsFixed(0),
|
controller.totalRevenue.value.toStringAsFixed(0),
|
||||||
Icons.payments_rounded,
|
Icons.payments_rounded,
|
||||||
const Color(0xFFF59E0B), cs)),
|
cs.warning, cs)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -624,15 +625,15 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.my_location_rounded,
|
Icon(Icons.my_location_rounded,
|
||||||
size: 14, color: Color(0xFF10B981)),
|
size: 14, color: cs.success),
|
||||||
Container(
|
Container(
|
||||||
width: 2,
|
width: 2,
|
||||||
height: 16,
|
height: 16,
|
||||||
color: Colors.grey.withOpacity(0.3),
|
color: Colors.grey.withOpacity(0.3),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 2)),
|
margin: const EdgeInsets.symmetric(vertical: 2)),
|
||||||
const Icon(Icons.location_on_rounded,
|
Icon(Icons.location_on_rounded,
|
||||||
size: 14, color: Color(0xFFEF4444)),
|
size: 14, color: cs.danger),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
@@ -682,7 +683,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
title: 'السائق',
|
title: 'السائق',
|
||||||
name: ride.driverName,
|
name: ride.driverName,
|
||||||
phone: ride.driverPhone,
|
phone: ride.driverPhone,
|
||||||
color: const Color(0xFF3B82F6),
|
color: cs.info,
|
||||||
isAdmin: isAdmin,
|
isAdmin: isAdmin,
|
||||||
),
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
@@ -709,19 +710,19 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
_buildInfoChip(
|
_buildInfoChip(
|
||||||
Icons.payments_rounded,
|
Icons.payments_rounded,
|
||||||
'${double.tryParse(ride.price)?.toStringAsFixed(0) ?? 0} ل.س',
|
'${double.tryParse(ride.price)?.toStringAsFixed(0) ?? 0} ل.س',
|
||||||
const Color(0xFF10B981)),
|
cs.success),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildInfoChip(
|
_buildInfoChip(
|
||||||
Icons.straighten_rounded,
|
Icons.straighten_rounded,
|
||||||
'${double.tryParse(ride.distance)?.toStringAsFixed(1) ?? 0} كم',
|
'${double.tryParse(ride.distance)?.toStringAsFixed(1) ?? 0} كم',
|
||||||
const Color(0xFFF59E0B)),
|
cs.warning),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_buildInfoChip(
|
_buildInfoChip(
|
||||||
Icons.access_time_rounded,
|
Icons.access_time_rounded,
|
||||||
ride.time.length > 5
|
ride.time.length > 5
|
||||||
? ride.time.substring(0, 5)
|
? ride.time.substring(0, 5)
|
||||||
: ride.time,
|
: ride.time,
|
||||||
const Color(0xFF3B82F6)),
|
cs.info),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -735,16 +736,16 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFEF4444).withOpacity(0.08),
|
color: cs.danger.withOpacity(0.08),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xFFEF4444).withOpacity(0.2)),
|
color: cs.danger.withOpacity(0.2)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.info_outline_rounded,
|
Icon(Icons.info_outline_rounded,
|
||||||
size: 16, color: Color(0xFFEF4444)),
|
size: 16, color: cs.danger),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -859,14 +860,15 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
|
|
||||||
// Helper Methods for Status
|
// Helper Methods for Status
|
||||||
Color _getStatusColor(String status) {
|
Color _getStatusColor(String status) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
if (status == 'Begin' || status == 'Arrived') {
|
if (status == 'Begin' || status == 'Arrived') {
|
||||||
return const Color(0xFF10B981);
|
return cs.success;
|
||||||
}
|
}
|
||||||
if (status == 'Finished') return const Color(0xFF14B8A6);
|
if (status == 'Finished') return const Color(0xFF14B8A6);
|
||||||
if (status.contains('Cancel') || status == 'TimeOut') {
|
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;
|
return Colors.grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,6 +978,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -1031,8 +1034,8 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
blurRadius: 5),
|
blurRadius: 5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.flag_rounded,
|
child: Icon(Icons.flag_rounded,
|
||||||
color: Color(0xFF10B981), size: 24),
|
color: cs.success, size: 24),
|
||||||
),
|
),
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
),
|
),
|
||||||
@@ -1052,8 +1055,8 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
blurRadius: 5),
|
blurRadius: 5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.location_on_rounded,
|
child: Icon(Icons.location_on_rounded,
|
||||||
color: Color(0xFFEF4444), size: 24),
|
color: cs.danger, size: 24),
|
||||||
),
|
),
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
),
|
),
|
||||||
@@ -1069,12 +1072,12 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: const Color(0xFF3B82F6).withOpacity(0.3),
|
color: cs.info.withOpacity(0.3),
|
||||||
blurRadius: 8),
|
blurRadius: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.directions_car_rounded,
|
child: Icon(Icons.directions_car_rounded,
|
||||||
color: Color(0xFF3B82F6), size: 28),
|
color: cs.info, size: 28),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1110,7 +1113,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
title: 'السائق',
|
title: 'السائق',
|
||||||
name: widget.ride.driverName,
|
name: widget.ride.driverName,
|
||||||
phone: widget.ride.driverPhone,
|
phone: widget.ride.driverPhone,
|
||||||
color: const Color(0xFF3B82F6),
|
color: cs.info,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Passenger Info
|
// Passenger Info
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -303,13 +304,13 @@ class Rides extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
|
color: cs.warning.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
|
color: cs.warning.withValues(alpha: 0.25)),
|
||||||
),
|
),
|
||||||
child: Icon(Icons.analytics_rounded,
|
child: Icon(Icons.analytics_rounded,
|
||||||
color: const Color(0xFFF59E0B), size: 16),
|
color: cs.warning, size: 16),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
@@ -338,9 +339,9 @@ class Rides extends StatelessWidget {
|
|||||||
_DetailItem('إجمالي الرحلات', details['total_rides'] ?? '',
|
_DetailItem('إجمالي الرحلات', details['total_rides'] ?? '',
|
||||||
Icons.route_rounded, cs.primary),
|
Icons.route_rounded, cs.primary),
|
||||||
_DetailItem('رحلات نشطة', details['ongoing_rides'] ?? '',
|
_DetailItem('رحلات نشطة', details['ongoing_rides'] ?? '',
|
||||||
Icons.play_circle_rounded, const Color(0xFFF59E0B)),
|
Icons.play_circle_rounded, cs.warning),
|
||||||
_DetailItem('مكتملة', details['completed_rides'] ?? '',
|
_DetailItem('مكتملة', details['completed_rides'] ?? '',
|
||||||
Icons.check_circle_rounded, const Color(0xFF10B981)),
|
Icons.check_circle_rounded, cs.success),
|
||||||
_DetailItem('ملغاة', details['cancelled_rides'] ?? '',
|
_DetailItem('ملغاة', details['cancelled_rides'] ?? '',
|
||||||
Icons.cancel_rounded, cs.error),
|
Icons.cancel_rounded, cs.error),
|
||||||
_DetailItem('أطول مدة', details['longest_duration'] ?? '',
|
_DetailItem('أطول مدة', details['longest_duration'] ?? '',
|
||||||
@@ -348,17 +349,17 @@ class Rides extends StatelessWidget {
|
|||||||
_DetailItem('إجمالي المسافة', '${details['total_distance'] ?? 0} km',
|
_DetailItem('إجمالي المسافة', '${details['total_distance'] ?? 0} km',
|
||||||
Icons.straighten_rounded, cs.primary),
|
Icons.straighten_rounded, cs.primary),
|
||||||
_DetailItem('متوسط المسافة', '${details['average_distance'] ?? 0} km',
|
_DetailItem('متوسط المسافة', '${details['average_distance'] ?? 0} km',
|
||||||
Icons.speed_rounded, const Color(0xFFF59E0B)),
|
Icons.speed_rounded, cs.warning),
|
||||||
_DetailItem('أطول مسافة', '${details['longest_distance'] ?? 0} km',
|
_DetailItem('أطول مسافة', '${details['longest_distance'] ?? 0} km',
|
||||||
Icons.arrow_forward_rounded, cs.tertiary),
|
Icons.arrow_forward_rounded, cs.tertiary),
|
||||||
_DetailItem('إجمالي أرباح السائقين',
|
_DetailItem('إجمالي أرباح السائقين',
|
||||||
'\$${details['total_driver_earnings'] ?? 0}', Icons.payments_rounded,
|
'\$${details['total_driver_earnings'] ?? 0}', Icons.payments_rounded,
|
||||||
const Color(0xFF10B981)),
|
cs.success),
|
||||||
_DetailItem('إجمالي أرباح الشركة',
|
_DetailItem('إجمالي أرباح الشركة',
|
||||||
'\$${details['total_company_earnings'] ?? 0}',
|
'\$${details['total_company_earnings'] ?? 0}',
|
||||||
Icons.business_center_rounded, cs.primary),
|
Icons.business_center_rounded, cs.primary),
|
||||||
_DetailItem('نسبة الشركة', '${details['companyPercent'] ?? 0}%',
|
_DetailItem('نسبة الشركة', '${details['companyPercent'] ?? 0}%',
|
||||||
Icons.pie_chart_rounded, const Color(0xFFF59E0B)),
|
Icons.pie_chart_rounded, cs.warning),
|
||||||
];
|
];
|
||||||
|
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../controller/server/server_monitor_controller.dart';
|
import '../../../controller/server/server_monitor_controller.dart';
|
||||||
@@ -365,7 +366,7 @@ class _ServicesCard extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isActive
|
color: isActive
|
||||||
? const Color(0xFF10B981).withValues(alpha: 0.3)
|
? cs.success.withValues(alpha: 0.3)
|
||||||
: cs.error.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),
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isActive
|
color: isActive
|
||||||
? const Color(0xFF10B981).withValues(alpha: 0.1)
|
? cs.success.withValues(alpha: 0.1)
|
||||||
: cs.error.withValues(alpha: 0.1),
|
: cs.error.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:siro_admin/controller/admin/analytics_v2_controller.dart';
|
import 'package:siro_admin/controller/admin/analytics_v2_controller.dart';
|
||||||
|
|
||||||
class AdvancedAnalyticsPage extends StatelessWidget {
|
class AdvancedAnalyticsPage extends StatelessWidget {
|
||||||
@@ -17,6 +18,9 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
_buildAppBar(context, cs),
|
_buildAppBar(context, cs),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GetBuilder<AnalyticsV2Controller>(
|
child: GetBuilder<AnalyticsV2Controller>(
|
||||||
|
init: Get.isRegistered<AnalyticsV2Controller>()
|
||||||
|
? Get.find<AnalyticsV2Controller>()
|
||||||
|
: AnalyticsV2Controller(),
|
||||||
builder: (ctrl) {
|
builder: (ctrl) {
|
||||||
if (ctrl.isLoading) {
|
if (ctrl.isLoading) {
|
||||||
return _buildLoadingState(cs);
|
return _buildLoadingState(cs);
|
||||||
@@ -178,7 +182,7 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
_buildSummaryCard(
|
_buildSummaryCard(
|
||||||
'صافي الربح',
|
'صافي الربح',
|
||||||
'${summary['total_profit_all'] ?? 0}',
|
'${summary['total_profit_all'] ?? 0}',
|
||||||
const Color(0xFF10B981),
|
cs.success,
|
||||||
Icons.trending_up_rounded,
|
Icons.trending_up_rounded,
|
||||||
cs,
|
cs,
|
||||||
),
|
),
|
||||||
@@ -285,7 +289,7 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_LegendDot(color: cs.tertiary, label: 'الإيرادات'),
|
_LegendDot(color: cs.tertiary, label: 'الإيرادات'),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
_LegendDot(color: const Color(0xFF10B981), label: 'الربح'),
|
_LegendDot(color: cs.success, label: 'الربح'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -356,13 +360,13 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
LineChartBarData(
|
LineChartBarData(
|
||||||
spots: profitSpots,
|
spots: profitSpots,
|
||||||
isCurved: true,
|
isCurved: true,
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
barWidth: 3,
|
barWidth: 3,
|
||||||
isStrokeCapRound: true,
|
isStrokeCapRound: true,
|
||||||
dotData: const FlDotData(show: false),
|
dotData: const FlDotData(show: false),
|
||||||
belowBarData: BarAreaData(
|
belowBarData: BarAreaData(
|
||||||
show: true,
|
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(
|
BarChartRodData(
|
||||||
toY: dCount,
|
toY: dCount,
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.7),
|
color: cs.warning.withValues(alpha: 0.7),
|
||||||
width: 8,
|
width: 8,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
@@ -450,7 +454,7 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
_LegendDot(color: cs.tertiary, label: 'ركاب جدد'),
|
_LegendDot(color: cs.tertiary, label: 'ركاب جدد'),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
_LegendDot(
|
_LegendDot(
|
||||||
color: const Color(0xFFF59E0B), label: 'سائقين جدد'),
|
color: cs.warning, label: 'سائقين جدد'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -532,7 +536,7 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
final d = drivers[i];
|
final d = drivers[i];
|
||||||
final isTop3 = i < 3;
|
final isTop3 = i < 3;
|
||||||
final rankColors = [
|
final rankColors = [
|
||||||
const Color(0xFFF59E0B),
|
cs.warning,
|
||||||
cs.onSurfaceVariant,
|
cs.onSurfaceVariant,
|
||||||
const Color(0xFFCD7F32),
|
const Color(0xFFCD7F32),
|
||||||
];
|
];
|
||||||
@@ -624,7 +628,7 @@ class AdvancedAnalyticsPage extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'${d['total_revenue']} ل.س',
|
'${d['total_revenue']} ل.س',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
@@ -62,7 +63,7 @@ class StaticDash extends StatelessWidget {
|
|||||||
spots: c.chartDataDrivers,
|
spots: c.chartDataDrivers,
|
||||||
compareSpots:
|
compareSpots:
|
||||||
c.isComparing ? c.chartDataDriversCompare : null,
|
c.isComparing ? c.chartDataDriversCompare : null,
|
||||||
color: const Color(0xFFF59E0B),
|
color: cs.warning,
|
||||||
icon: Icons.drive_eta_rounded,
|
icon: Icons.drive_eta_rounded,
|
||||||
controller: c,
|
controller: c,
|
||||||
cs: cs,
|
cs: cs,
|
||||||
@@ -146,7 +147,7 @@ class StaticDash extends StatelessWidget {
|
|||||||
compareSpots: c.isComparing
|
compareSpots: c.isComparing
|
||||||
? c.chartDataDriversMatchingNotesCompare
|
? c.chartDataDriversMatchingNotesCompare
|
||||||
: null,
|
: null,
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
icon: Icons.phone_in_talk_rounded,
|
icon: Icons.phone_in_talk_rounded,
|
||||||
controller: c,
|
controller: c,
|
||||||
cs: cs,
|
cs: cs,
|
||||||
@@ -354,7 +355,7 @@ class _KpiRow extends StatelessWidget {
|
|||||||
_KpiItem('الركاب', controller.totalMonthlyPassengers,
|
_KpiItem('الركاب', controller.totalMonthlyPassengers,
|
||||||
Icons.groups_rounded, cs.tertiary),
|
Icons.groups_rounded, cs.tertiary),
|
||||||
_KpiItem('السائقون', controller.totalMonthlyDrivers,
|
_KpiItem('السائقون', controller.totalMonthlyDrivers,
|
||||||
Icons.drive_eta_rounded, const Color(0xFFF59E0B)),
|
Icons.drive_eta_rounded, cs.warning),
|
||||||
_KpiItem(
|
_KpiItem(
|
||||||
'الرحلات', controller.totalMonthlyRides, Icons.map_rounded, cs.primary),
|
'الرحلات', controller.totalMonthlyRides, Icons.map_rounded, cs.primary),
|
||||||
];
|
];
|
||||||
@@ -822,13 +823,13 @@ class _EmployeeLeaderboard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
|
color: cs.warning.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(9),
|
borderRadius: BorderRadius.circular(9),
|
||||||
border: Border.all(
|
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,
|
child: Icon(Icons.emoji_events_rounded,
|
||||||
color: Color(0xFFF59E0B), size: 16),
|
color: cs.warning, size: 16),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Column(
|
Column(
|
||||||
@@ -930,7 +931,7 @@ class _RankBadge extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isTop3 = rank <= 3;
|
final isTop3 = rank <= 3;
|
||||||
final medalColors = [
|
final medalColors = [
|
||||||
const Color(0xFFF59E0B),
|
cs.warning,
|
||||||
cs.onSurfaceVariant,
|
cs.onSurfaceVariant,
|
||||||
const Color(0xFFCD7F32)
|
const Color(0xFFCD7F32)
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../controller/admin/wallet_admin_controller.dart';
|
import '../../../controller/admin/wallet_admin_controller.dart';
|
||||||
@@ -195,14 +196,14 @@ class Wallet extends StatelessWidget {
|
|||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
colors: [
|
colors: [
|
||||||
const Color(0xFF10B981).withValues(alpha: 0.20),
|
cs.success.withValues(alpha: 0.20),
|
||||||
const Color(0xFF10B981).withValues(alpha: 0.08),
|
cs.success.withValues(alpha: 0.08),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
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),
|
const SizedBox(width: 14),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -234,7 +235,7 @@ class Wallet extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
amount,
|
amount,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
),
|
),
|
||||||
@@ -242,7 +243,7 @@ class Wallet extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'ل.س',
|
'ل.س',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF10B981).withValues(alpha: 0.6),
|
color: cs.success.withValues(alpha: 0.6),
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../constant/links.dart';
|
import '../../constant/links.dart';
|
||||||
@@ -447,7 +448,7 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
|
|||||||
Text(
|
Text(
|
||||||
amount,
|
amount,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF10B981),
|
color: cs.success,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
),
|
),
|
||||||
@@ -455,7 +456,7 @@ class _InvoiceListPageState extends State<InvoiceListPage> {
|
|||||||
Text(
|
Text(
|
||||||
"د.أ",
|
"د.أ",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: const Color(0xFF10B981).withValues(alpha: 0.6),
|
color: cs.success.withValues(alpha: 0.6),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ class _TransitOrgListPageState extends State<TransitOrgListPage> {
|
|||||||
Color statusColor;
|
Color statusColor;
|
||||||
switch (org.contractStatus) {
|
switch (org.contractStatus) {
|
||||||
case 'active':
|
case 'active':
|
||||||
statusColor = const Color(0xFF10B981);
|
statusColor = cs.success;
|
||||||
break;
|
break;
|
||||||
case 'trial':
|
case 'trial':
|
||||||
statusColor = cs.tertiary;
|
statusColor = cs.tertiary;
|
||||||
|
|||||||
Reference in New Issue
Block a user