Files
tripz-llc/backend/Admin/v2/analytics/dashboard_data.php
T
Hamza-AyedandClaude Opus 5 4d8414c96b feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:14:13 +03:00

120 lines
4.1 KiB
PHP

<?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']);
}