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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9909d9b4c1
commit
4d8414c96b
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,44 @@
|
||||
<?php
|
||||
// Admin/v2/analytics/driver_ranking.php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// أفضل 10 كباتن حسب عدد الرحلات المكتملة
|
||||
$stmt = $con->prepare("
|
||||
SELECT
|
||||
d.id, d.first_name, d.last_name, d.phone,
|
||||
COUNT(r.id) as completed_rides,
|
||||
SUM(r.price) as total_revenue
|
||||
FROM driver d
|
||||
JOIN ride r ON d.id = r.driver_id
|
||||
WHERE LOWER(r.status) IN ('finished','completed')
|
||||
GROUP BY d.id, d.first_name, d.last_name, d.phone
|
||||
ORDER BY completed_rides DESC
|
||||
LIMIT 10
|
||||
");
|
||||
$stmt->execute();
|
||||
$top_drivers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// فك تشفير الأسماء
|
||||
foreach ($top_drivers as &$driver) {
|
||||
$driver['first_name'] = $encryptionHelper->decryptData($driver['first_name']);
|
||||
$driver['last_name'] = $encryptionHelper->decryptData($driver['last_name']);
|
||||
$driver['phone'] = $encryptionHelper->decryptData($driver['phone']);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => $top_drivers
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
error_log("[driver_ranking.php] " . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// Admin/v2/analytics/growth.php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// نمو الركاب لآخر 30 يوم
|
||||
$stmt = $con->prepare("
|
||||
SELECT DATE(created_at) as date, COUNT(*) as new_passengers
|
||||
FROM passengers
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
");
|
||||
$stmt->execute();
|
||||
$passenger_growth = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// نمو السائقين لآخر 30 يوم
|
||||
$stmt = $con->prepare("
|
||||
SELECT DATE(created_at) as date, COUNT(*) as new_drivers
|
||||
FROM driver
|
||||
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
");
|
||||
$stmt->execute();
|
||||
$driver_growth = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// إجمالي الأعداد الحالية
|
||||
$stmt = $con->prepare("SELECT COUNT(*) FROM passengers");
|
||||
$stmt->execute();
|
||||
$total_passengers = $stmt->fetchColumn();
|
||||
|
||||
$stmt = $con->prepare("SELECT COUNT(*) FROM driver");
|
||||
$stmt->execute();
|
||||
$total_drivers = $stmt->fetchColumn();
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'passenger_daily' => $passenger_growth,
|
||||
'driver_daily' => $driver_growth,
|
||||
'totals' => [
|
||||
'passengers' => (int)$total_passengers,
|
||||
'drivers' => (int)$total_drivers
|
||||
]
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
error_log("[growth.php] " . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred']);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// Admin/v2/analytics/revenue.php
|
||||
require_once __DIR__ . '/../../../connect.php';
|
||||
|
||||
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Unauthorized access.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// إحصائيات الإيرادات لآخر 30 يوم
|
||||
$stmt = $con->prepare("
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
SUM(price) as total_revenue,
|
||||
SUM(price - price_for_driver) as company_profit,
|
||||
COUNT(*) as total_rides
|
||||
FROM ride
|
||||
WHERE LOWER(status) IN ('finished','completed')
|
||||
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date ASC
|
||||
");
|
||||
$stmt->execute();
|
||||
$daily_stats = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// ملخص عام
|
||||
$stmt = $con->prepare("
|
||||
SELECT
|
||||
SUM(price) as total_revenue_all,
|
||||
SUM(price - price_for_driver) as total_profit_all,
|
||||
AVG(price) as avg_ride_price
|
||||
FROM ride
|
||||
WHERE LOWER(status) IN ('finished','completed')
|
||||
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
||||
");
|
||||
$stmt->execute();
|
||||
$summary = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'daily' => $daily_stats,
|
||||
'summary' => $summary
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
error_log("[revenue.php] " . $e->getMessage());
|
||||
echo json_encode(['status' => 'error', 'message' => 'An internal error occurred']);
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user