Update: 2026-07-10 23:59:23
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* dashboard_data.php
|
||||
* API موحّد للداشبورد التحليلي — يقرأ من ملفات JSON المؤرشفة + بيانات حيّة من Redis.
|
||||
*
|
||||
* Parameters:
|
||||
* date (optional) — YYYY-MM-DD, default: today
|
||||
* section (optional) — realtime|gap|heatmap|pricing|revenue|growth|market|complaints|funnel|hourly|weekly|zones|retention|all
|
||||
* default: all
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$requestedDate = filterRequest('date') ?: date('Y-m-d');
|
||||
$section = filterRequest('section') ?: 'all';
|
||||
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $requestedDate)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid date format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$cacheBase = __DIR__ . '/../../../cache/analytics';
|
||||
$dayDir = "$cacheBase/$requestedDate";
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
'date' => $requestedDate,
|
||||
'section' => $section,
|
||||
'data' => [],
|
||||
];
|
||||
|
||||
function loadSnapshot(string $dir, string $name): ?array {
|
||||
$path = "$dir/$name.json";
|
||||
if (!file_exists($path)) return null;
|
||||
$data = json_decode(file_get_contents($path), true);
|
||||
return is_array($data) ? $data : null;
|
||||
}
|
||||
|
||||
function loadLatestRealtime(string $dir): ?array {
|
||||
$files = glob("$dir/realtime_*.json");
|
||||
if (empty($files)) return null;
|
||||
sort($files);
|
||||
$latest = end($files);
|
||||
$data = json_decode(file_get_contents($latest), true);
|
||||
return is_array($data) ? $data : null;
|
||||
}
|
||||
|
||||
$sectionMap = [
|
||||
'realtime' => fn() => loadLatestRealtime($dayDir),
|
||||
'gap' => fn() => loadSnapshot($dayDir, 'supply_demand_gap'),
|
||||
'heatmap' => fn() => loadSnapshot($dayDir, 'heatmap'),
|
||||
'pricing' => fn() => loadSnapshot($dayDir, 'pricing_grids'),
|
||||
'demand' => fn() => loadSnapshot($dayDir, 'predictive_demand'),
|
||||
'revenue' => fn() => loadSnapshot($dayDir, 'revenue_30d'),
|
||||
'growth' => fn() => loadSnapshot($dayDir, 'growth_30d'),
|
||||
'market' => fn() => loadSnapshot($dayDir, 'market_health'),
|
||||
'complaints' => fn() => loadSnapshot($dayDir, 'complaints_open'),
|
||||
'funnel' => fn() => loadSnapshot($dayDir, 'ride_funnel'),
|
||||
'hourly' => fn() => loadSnapshot($dayDir, 'hourly_pattern'),
|
||||
'weekly' => fn() => loadSnapshot($dayDir, 'weekly_comparison'),
|
||||
'zones' => fn() => loadSnapshot($dayDir, 'top_zones'),
|
||||
'retention' => fn() => loadSnapshot($dayDir, 'retention_cohort'),
|
||||
'competitor' => fn() => loadSnapshot($dayDir, 'competitor_prices_24h'),
|
||||
];
|
||||
|
||||
try {
|
||||
if (!is_dir($dayDir)) {
|
||||
$response['data'] = null;
|
||||
$response['note'] = "No snapshot data for $requestedDate";
|
||||
|
||||
$indexPath = "$cacheBase/index.json";
|
||||
if (file_exists($indexPath)) {
|
||||
$idx = json_decode(file_get_contents($indexPath), true);
|
||||
$response['available_dates'] = $idx['available_dates'] ?? [];
|
||||
}
|
||||
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($section === 'all') {
|
||||
foreach ($sectionMap as $key => $loader) {
|
||||
$result = $loader();
|
||||
if ($result !== null) {
|
||||
$response['data'][$key] = $result;
|
||||
}
|
||||
}
|
||||
} elseif (isset($sectionMap[$section])) {
|
||||
$response['data'] = $sectionMap[$section]();
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => "Unknown section: $section",
|
||||
'available' => array_keys($sectionMap),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$indexPath = "$cacheBase/index.json";
|
||||
if (file_exists($indexPath)) {
|
||||
$idx = json_decode(file_get_contents($indexPath), true);
|
||||
$response['available_dates'] = $idx['available_dates'] ?? [];
|
||||
}
|
||||
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
error_log("[dashboard_data.php] " . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'Internal error']);
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* cron_dashboard_snapshot.php
|
||||
* يجمع بيانات التحليلات من Redis وقاعدة البيانات ويخزنها كملفات JSON مؤرشفة.
|
||||
*
|
||||
* الجدولة المقترحة:
|
||||
* - لقطة سريعة (ساعي): 0 * * * * php cron_dashboard_snapshot.php hourly
|
||||
* - لقطة يومية: 0 3 * * * php cron_dashboard_snapshot.php daily
|
||||
* - لقطة أسبوعية: 0 4 * * 1 php cron_dashboard_snapshot.php weekly
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../functions.php';
|
||||
|
||||
set_time_limit(180);
|
||||
ini_set('memory_limit', '256M');
|
||||
|
||||
$mode = $argv[1] ?? 'hourly';
|
||||
$today = date('Y-m-d');
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$cacheBase = __DIR__ . '/../cache/analytics';
|
||||
$todayDir = "$cacheBase/$today";
|
||||
|
||||
if (!is_dir($todayDir)) {
|
||||
mkdir($todayDir, 0755, true);
|
||||
}
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
$conRide = Database::get('ride');
|
||||
$redis = getRedisConnection();
|
||||
} catch (Exception $e) {
|
||||
die("[DashboardSnapshot] Connection failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
echo "[DashboardSnapshot] Mode: $mode | $now\n";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// الوظائف المساعدة
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
function saveSnapshot(string $filename, array $data, string $dir): void {
|
||||
$data['_generated_at'] = date('Y-m-d H:i:s');
|
||||
$data['_mode'] = $GLOBALS['mode'];
|
||||
$path = "$dir/$filename";
|
||||
file_put_contents($path, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
echo " ✓ Saved: $path\n";
|
||||
}
|
||||
|
||||
function safeQuery(PDO $db, string $sql, array $params = []): array {
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function safeScalar(PDO $db, string $sql, array $params = []) {
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 1. لقطة اللحظة الحالية (كل ساعة)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if (in_array($mode, ['hourly', 'daily', 'weekly'])) {
|
||||
echo "[Snapshot] Realtime stats...\n";
|
||||
|
||||
$activeRides = (int)safeScalar($conRide, "SELECT COUNT(*) FROM ride WHERE status IN ('wait','started','arrived')");
|
||||
$onlineDrivers = (int)safeScalar($con, "SELECT COUNT(*) FROM car_locations WHERE status = 'on'");
|
||||
$revenueToday = (float)safeScalar($conRide, "SELECT IFNULL(SUM(price_for_passenger),0) FROM ride WHERE status='Finished' AND DATE(created_at)=CURDATE()");
|
||||
$revenueYesterday = (float)safeScalar($conRide, "SELECT IFNULL(SUM(price_for_passenger),0) FROM ride WHERE status='Finished' AND DATE(created_at)=DATE_SUB(CURDATE(), INTERVAL 1 DAY)");
|
||||
$openComplaints = (int)safeScalar($con, "SELECT COUNT(*) FROM complaint WHERE statusComplaint='Open'");
|
||||
$expiringLicenses = (int)safeScalar($con, "SELECT COUNT(*) FROM driver WHERE expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 15 DAY)");
|
||||
|
||||
$hour = date('H');
|
||||
saveSnapshot("realtime_{$hour}.json", [
|
||||
'active_rides' => $activeRides,
|
||||
'online_drivers' => $onlineDrivers,
|
||||
'revenue_today' => $revenueToday,
|
||||
'revenue_yesterday' => $revenueYesterday,
|
||||
'open_complaints' => $openComplaints,
|
||||
'expiring_licenses' => $expiringLicenses,
|
||||
], $todayDir);
|
||||
|
||||
// ─── فجوة العرض والطلب (Supply-Demand Gap) ───
|
||||
echo "[Snapshot] Supply-Demand gap...\n";
|
||||
|
||||
$GRID = 0.01;
|
||||
$demandRaw = safeQuery($conRide, "
|
||||
SELECT
|
||||
ROUND(SUBSTRING_INDEX(start_location,',',1) / $GRID) * $GRID AS lat,
|
||||
ROUND(SUBSTRING_INDEX(start_location,',',-1) / $GRID) * $GRID AS lng,
|
||||
COUNT(*) AS demand
|
||||
FROM ride
|
||||
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR)
|
||||
AND start_location IS NOT NULL AND start_location != ''
|
||||
GROUP BY lat, lng
|
||||
HAVING demand >= 1
|
||||
");
|
||||
|
||||
$supplyRaw = safeQuery($con, "
|
||||
SELECT
|
||||
ROUND(latitude / $GRID) * $GRID AS lat,
|
||||
ROUND(longitude / $GRID) * $GRID AS lng,
|
||||
COUNT(*) AS supply
|
||||
FROM car_locations
|
||||
WHERE status = 'on'
|
||||
AND latitude != 0 AND longitude != 0
|
||||
GROUP BY lat, lng
|
||||
");
|
||||
|
||||
$supplyMap = [];
|
||||
foreach ($supplyRaw as $s) {
|
||||
$key = $s['lat'] . '_' . $s['lng'];
|
||||
$supplyMap[$key] = (int)$s['supply'];
|
||||
}
|
||||
|
||||
$gapCells = [];
|
||||
foreach ($demandRaw as $d) {
|
||||
$key = $d['lat'] . '_' . $d['lng'];
|
||||
$supply = $supplyMap[$key] ?? 0;
|
||||
$demand = (int)$d['demand'];
|
||||
$gap = $demand - $supply;
|
||||
$gapCells[] = [
|
||||
'lat' => (float)$d['lat'],
|
||||
'lng' => (float)$d['lng'],
|
||||
'demand' => $demand,
|
||||
'supply' => $supply,
|
||||
'gap' => $gap,
|
||||
'ratio' => $supply > 0 ? round($demand / $supply, 2) : ($demand > 0 ? 99.0 : 0),
|
||||
];
|
||||
}
|
||||
|
||||
usort($gapCells, fn($a, $b) => $b['gap'] <=> $a['gap']);
|
||||
$gapCells = array_slice($gapCells, 0, 200);
|
||||
|
||||
saveSnapshot("supply_demand_gap.json", [
|
||||
'cells' => $gapCells,
|
||||
'total_demand_cells' => count($demandRaw),
|
||||
'total_supply_cells' => count($supplyRaw),
|
||||
], $todayDir);
|
||||
|
||||
// ─── كاش الخريطة الحرارية من Redis ───
|
||||
echo "[Snapshot] Heatmap cache from Redis...\n";
|
||||
$heatmapRedis = $redis ? $redis->get('siro:cache:heatmap:data') : null;
|
||||
if ($heatmapRedis) {
|
||||
$heatmapData = json_decode($heatmapRedis, true);
|
||||
saveSnapshot("heatmap.json", $heatmapData ?: [], $todayDir);
|
||||
}
|
||||
|
||||
// ─── كاش التسعير من Redis ───
|
||||
echo "[Snapshot] Pricing cache from Redis...\n";
|
||||
$pricingRedis = $redis ? $redis->get('siro:cache:pricing:grids') : null;
|
||||
if ($pricingRedis) {
|
||||
$pricingData = json_decode($pricingRedis, true);
|
||||
saveSnapshot("pricing_grids.json", $pricingData ?: [], $todayDir);
|
||||
}
|
||||
|
||||
// ─── بيانات الكرون Predictive Demand من Redis ───
|
||||
$predictiveRedis = $redis ? $redis->get('siro:predictive_demand:latest') : null;
|
||||
if ($predictiveRedis) {
|
||||
saveSnapshot("predictive_demand.json", json_decode($predictiveRedis, true) ?: [], $todayDir);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2. لقطة يومية (تحليلات ثقيلة)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if (in_array($mode, ['daily', 'weekly'])) {
|
||||
echo "[Snapshot] Daily analytics...\n";
|
||||
|
||||
// ─── الإيرادات اليومية (30 يوم) ───
|
||||
$revenueDaily = safeQuery($conRide, "
|
||||
SELECT DATE(created_at) as date,
|
||||
SUM(price) as total_revenue,
|
||||
SUM(price - price_for_driver) as company_profit,
|
||||
COUNT(*) as total_rides,
|
||||
AVG(price) as avg_fare
|
||||
FROM ride
|
||||
WHERE status = 'Finished' AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
");
|
||||
saveSnapshot("revenue_30d.json", ['daily' => $revenueDaily], $todayDir);
|
||||
|
||||
// ─── النمو (ركاب + كباتن) ───
|
||||
$passengerGrowth = safeQuery($con, "
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM passengers
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at) ORDER BY date ASC
|
||||
");
|
||||
$driverGrowth = safeQuery($con, "
|
||||
SELECT DATE(created_at) as date, COUNT(*) as count
|
||||
FROM driver
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at) ORDER BY date ASC
|
||||
");
|
||||
$totalPassengers = (int)safeScalar($con, "SELECT COUNT(*) FROM passengers");
|
||||
$totalDrivers = (int)safeScalar($con, "SELECT COUNT(*) FROM driver");
|
||||
|
||||
saveSnapshot("growth_30d.json", [
|
||||
'passengers' => $passengerGrowth,
|
||||
'drivers' => $driverGrowth,
|
||||
'totals' => ['passengers' => $totalPassengers, 'drivers' => $totalDrivers],
|
||||
], $todayDir);
|
||||
|
||||
// ─── توزيع الرحلات حسب الساعة (لاكتشاف الأنماط) ───
|
||||
$hourlyPattern = safeQuery($conRide, "
|
||||
SELECT HOUR(created_at) as hour, COUNT(*) as rides,
|
||||
AVG(price) as avg_price
|
||||
FROM ride
|
||||
WHERE status = 'Finished' AND created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||
GROUP BY HOUR(created_at) ORDER BY hour
|
||||
");
|
||||
saveSnapshot("hourly_pattern.json", ['hours' => $hourlyPattern], $todayDir);
|
||||
|
||||
// ─── توزيع حالات الرحلات (Funnel) ───
|
||||
$rideFunnel = safeQuery($conRide, "
|
||||
SELECT status, COUNT(*) as count
|
||||
FROM ride
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
|
||||
GROUP BY status
|
||||
");
|
||||
saveSnapshot("ride_funnel.json", ['statuses' => $rideFunnel], $todayDir);
|
||||
|
||||
// ─── أسعار المنافسين (تاريخي ٢٤ ساعة) ───
|
||||
$competitorHourly = safeQuery($con, "
|
||||
SELECT DATE_FORMAT(created_at,'%Y-%m-%d %H:00:00') AS hour_bucket,
|
||||
competitor_name,
|
||||
AVG(price_per_km) AS avg_price,
|
||||
COUNT(*) AS samples
|
||||
FROM scraped_competitor_prices
|
||||
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||
GROUP BY hour_bucket, competitor_name
|
||||
ORDER BY hour_bucket ASC
|
||||
");
|
||||
saveSnapshot("competitor_prices_24h.json", ['hourly' => $competitorHourly], $todayDir);
|
||||
|
||||
// ─── صحة السوق (market health) ───
|
||||
$countries = ['JO', 'SY', 'EG', 'IQ'];
|
||||
$marketHealth = [];
|
||||
foreach ($countries as $cc) {
|
||||
$rows = safeQuery($con, "
|
||||
SELECT report_date, average_pci, market_share_percent, total_anomalies
|
||||
FROM market_health_reports
|
||||
WHERE country_code = :cc
|
||||
ORDER BY report_date DESC LIMIT 12
|
||||
", [':cc' => $cc]);
|
||||
if (!empty($rows)) {
|
||||
$marketHealth[$cc] = array_reverse($rows);
|
||||
}
|
||||
}
|
||||
saveSnapshot("market_health.json", ['countries' => $marketHealth], $todayDir);
|
||||
|
||||
// ─── شكاوى مفتوحة ───
|
||||
$complaints = safeQuery($con, "
|
||||
SELECT complaint_type, COUNT(*) as count
|
||||
FROM complaint
|
||||
WHERE statusComplaint = 'Open'
|
||||
GROUP BY complaint_type
|
||||
ORDER BY count DESC
|
||||
");
|
||||
saveSnapshot("complaints_open.json", ['by_type' => $complaints], $todayDir);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 3. لقطة أسبوعية (مقارنات وتجميعات)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
if ($mode === 'weekly') {
|
||||
echo "[Snapshot] Weekly deep analytics...\n";
|
||||
|
||||
// ─── مقارنة أسبوع بأسبوع ───
|
||||
$weeklyComparison = safeQuery($conRide, "
|
||||
SELECT
|
||||
YEARWEEK(created_at, 1) as yw,
|
||||
MIN(DATE(created_at)) as week_start,
|
||||
COUNT(*) as rides,
|
||||
SUM(price) as revenue,
|
||||
SUM(price - price_for_driver) as profit,
|
||||
COUNT(DISTINCT driver_id) as active_drivers,
|
||||
COUNT(DISTINCT passenger_id) as active_passengers
|
||||
FROM ride
|
||||
WHERE status = 'Finished' AND created_at >= DATE_SUB(CURDATE(), INTERVAL 12 WEEK)
|
||||
GROUP BY yw ORDER BY yw ASC
|
||||
");
|
||||
saveSnapshot("weekly_comparison.json", ['weeks' => $weeklyComparison], $todayDir);
|
||||
|
||||
// ─── أعلى مناطق (أكثر 20 خلية نشاطاً) ───
|
||||
$topZones = safeQuery($conRide, "
|
||||
SELECT
|
||||
ROUND(SUBSTRING_INDEX(start_location,',',1) / 0.01) * 0.01 AS lat,
|
||||
ROUND(SUBSTRING_INDEX(start_location,',',-1) / 0.01) * 0.01 AS lng,
|
||||
COUNT(*) AS rides,
|
||||
AVG(price) AS avg_price
|
||||
FROM ride
|
||||
WHERE status = 'Finished'
|
||||
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 4 WEEK)
|
||||
AND start_location IS NOT NULL AND start_location != ''
|
||||
GROUP BY lat, lng
|
||||
ORDER BY rides DESC
|
||||
LIMIT 20
|
||||
");
|
||||
saveSnapshot("top_zones.json", ['zones' => $topZones], $todayDir);
|
||||
|
||||
// ─── احتفاظ الركاب (Retention) ───
|
||||
$retention = safeQuery($conRide, "
|
||||
SELECT
|
||||
weeks_since_signup,
|
||||
COUNT(DISTINCT passenger_id) as active_passengers
|
||||
FROM (
|
||||
SELECT r.passenger_id,
|
||||
FLOOR(DATEDIFF(r.created_at, p.created_at) / 7) as weeks_since_signup
|
||||
FROM ride r
|
||||
JOIN passengers p ON r.passenger_id = p.id
|
||||
WHERE r.status = 'Finished'
|
||||
AND r.created_at >= DATE_SUB(CURDATE(), INTERVAL 12 WEEK)
|
||||
) sub
|
||||
GROUP BY weeks_since_signup
|
||||
ORDER BY weeks_since_signup
|
||||
");
|
||||
saveSnapshot("retention_cohort.json", ['cohorts' => $retention], $todayDir);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 4. إنشاء manifest يسهّل على الداشبورد معرفة الملفات المتاحة
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
$files = glob("$todayDir/*.json");
|
||||
$manifest = [];
|
||||
foreach ($files as $f) {
|
||||
$name = basename($f, '.json');
|
||||
if ($name === 'manifest') continue;
|
||||
$manifest[$name] = [
|
||||
'file' => basename($f),
|
||||
'size' => filesize($f),
|
||||
'updated' => date('Y-m-d H:i:s', filemtime($f)),
|
||||
];
|
||||
}
|
||||
saveSnapshot("manifest.json", ['snapshots' => $manifest, 'date' => $today], $todayDir);
|
||||
|
||||
// ─── فهرس الأيام المتاحة (لتسهيل التصفح التاريخي) ───
|
||||
$days = array_map('basename', glob("$cacheBase/20*", GLOB_ONLYDIR));
|
||||
rsort($days);
|
||||
file_put_contents("$cacheBase/index.json", json_encode([
|
||||
'available_dates' => $days,
|
||||
'latest' => $days[0] ?? $today,
|
||||
'updated' => $now,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||
|
||||
echo "[DashboardSnapshot] Done! Files in $todayDir\n";
|
||||
@@ -411,6 +411,8 @@ class AppLink {
|
||||
"$server/Admin/v2/quality/blacklist_manager.php";
|
||||
static String driverScorecard =
|
||||
"$server/Admin/v2/quality/driver_scorecard.php";
|
||||
static String analyticsDashboard =
|
||||
"$server/Admin/v2/analytics/dashboard/index.php";
|
||||
static String getEmployee = "$server/Admin/employee/get.php";
|
||||
static String getBestDriver = "$server/Admin/driver/getBestDriver.php";
|
||||
static String getBestDriverGiza =
|
||||
|
||||
@@ -35,6 +35,8 @@ import 'dashboard_v2_widget.dart';
|
||||
import 'static/advanced_analytics_page.dart';
|
||||
import 'financial/financial_v2_page.dart';
|
||||
import 'security/audit_logs_page.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../constant/links.dart';
|
||||
|
||||
class AdminHomePage extends StatefulWidget {
|
||||
const AdminHomePage({super.key});
|
||||
@@ -778,6 +780,12 @@ class _AdminHomePageState extends State<AdminHomePage>
|
||||
}),
|
||||
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, _info,
|
||||
() => Get.to(() => const AdvancedAnalyticsPage())),
|
||||
ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded,
|
||||
const Color(0xFF00CEC9), () async {
|
||||
final token = box.read(BoxName.jwt) ?? '';
|
||||
final url = '${AppLink.analyticsDashboard}?token=$token';
|
||||
await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
|
||||
}),
|
||||
],
|
||||
),
|
||||
ActionCategory(
|
||||
|
||||
Reference in New Issue
Block a user