first commit
This commit is contained in:
73
backend/ride/location/add.php
Executable file
73
backend/ride/location/add.php
Executable file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$latitude = filterRequest("latitude");
|
||||
$longitude = filterRequest("longitude");
|
||||
$status = filterRequest("status");
|
||||
$heading = filterRequest("heading");
|
||||
$speed = filterRequest("speed");
|
||||
|
||||
// distance قد تأتي باسم totalDistance من التطبيق
|
||||
$distanceRaw = filterRequest("distance");
|
||||
if ($distanceRaw === null || $distanceRaw === '') {
|
||||
$distanceRaw = filterRequest("totalDistance");
|
||||
}
|
||||
|
||||
// تطبيع القيم الرقمية
|
||||
$norm = function($v, $default = 0.0) {
|
||||
if ($v === null) return $default;
|
||||
$v = str_replace(',', '.', trim((string)$v));
|
||||
return is_numeric($v) ? (float)$v : $default;
|
||||
};
|
||||
|
||||
$lat = $norm($latitude, 0.0);
|
||||
$lng = $norm($longitude, 0.0);
|
||||
$head = $norm($heading, 0.0);
|
||||
$spd = $norm($speed, 0.0);
|
||||
|
||||
// ✅ قرّب لمرتين عشريتين ليتطابق مع DECIMAL(10,2)
|
||||
$dist = round($norm($distanceRaw, 0.0), 2);
|
||||
|
||||
// ✅ حارس بسيط للمدى (اختياري)
|
||||
if ($dist > 99999999.99) { $dist = 99999999.99; }
|
||||
if ($dist < -99999999.99){ $dist = -99999999.99; }
|
||||
|
||||
if (empty($driver_id) || ($lat == 0.0 && $lng == 0.0)) {
|
||||
jsonError("Invalid payload");
|
||||
exit;
|
||||
}
|
||||
|
||||
$created_at = date("Y-m-d H:i:s");
|
||||
|
||||
$sql = "INSERT INTO `car_tracks`
|
||||
(`driver_id`,`latitude`,`longitude`,`heading`,`speed`,`distance`,`status`,`created_at`)
|
||||
VALUES
|
||||
(:driver_id,:latitude,:longitude,:heading,:speed,:distance,:status,:created_at)";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$ok = $stmt->execute([
|
||||
':driver_id' => $driver_id,
|
||||
':latitude' => $lat,
|
||||
':longitude' => $lng,
|
||||
':heading' => $head,
|
||||
':speed' => $spd,
|
||||
':distance' => $dist, // ← now DECIMAL(10,2)-friendly
|
||||
':status' => (string)($status ?? 'on'),
|
||||
':created_at' => $created_at,
|
||||
]);
|
||||
|
||||
if ($ok) {
|
||||
jsonSuccess(null, "car_tracks saved successfully");
|
||||
} else {
|
||||
jsonError("Failed to save car track");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("car_tracks insert error: " . $e->getMessage());
|
||||
jsonError("Database error");
|
||||
} catch (Throwable $e) {
|
||||
error_log("car_tracks insert fatal: " . $e->getMessage());
|
||||
jsonError("Server error");
|
||||
}
|
||||
34
backend/ride/location/addpassengerLocation.php
Executable file
34
backend/ride/location/addpassengerLocation.php
Executable file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$passengerId = filterRequest("passengerId");
|
||||
$lat = filterRequest("lat");
|
||||
$lng = filterRequest("lng");
|
||||
$rideId = filterRequest("rideId");
|
||||
|
||||
// Validate the latitude and longitude
|
||||
if ($lat === '' || $lng === '') {
|
||||
jsonError("Latitude and longitude cannot be empty");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prepare an SQL statement with placeholders
|
||||
$sql = "INSERT INTO `passengerlocation`( `passengerId`, `lat`, `lng`, `rideId`) VALUES (:passengerId, :lat, :lng, :rideId)";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// Bind the parameters to the SQL query
|
||||
$stmt->bindParam(':passengerId', $passengerId);
|
||||
$stmt->bindParam(':lat', $lat);
|
||||
$stmt->bindParam(':lng', $lng);
|
||||
$stmt->bindParam(':rideId', $rideId);
|
||||
|
||||
// Execute the statement
|
||||
if ($stmt->execute()) {
|
||||
// Print a success message
|
||||
jsonSuccess(null, "Passenger location saved successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError("Failed to save passenger location");
|
||||
}
|
||||
?>
|
||||
20
backend/ride/location/delete.php
Normal file
20
backend/ride/location/delete.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
$sql = "DELETE FROM `car_locations` WHERE `driver_id` = :driver_id";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([':driver_id' => $driver_id]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Print a success message
|
||||
jsonSuccess($message = "Car location deleted successfully");
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError($message = "Failed to delete car location");
|
||||
}
|
||||
|
||||
?>
|
||||
341
backend/ride/location/driversTime.html
Executable file
341
backend/ride/location/driversTime.html
Executable file
@@ -0,0 +1,341 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>متابعة السائقين - انطلق</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
body { font-family: 'Tajawal', sans-serif; background-color: #f8fafc; }
|
||||
.driver-card { transition: all 0.2s ease; }
|
||||
.driver-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); }
|
||||
/* Loading Spinner */
|
||||
.loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }
|
||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-slate-800">
|
||||
|
||||
<div class="bg-slate-900 text-white p-6 shadow-lg sticky top-0 z-50">
|
||||
<div class="container mx-auto flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold flex items-center gap-3">
|
||||
<i class="fa-solid fa-taxi text-yellow-400"></i>
|
||||
لوحة متابعة السائقين
|
||||
</h1>
|
||||
<p class="text-slate-400 text-xs mt-1 flex items-center gap-2">
|
||||
<i class="fa-solid fa-clock"></i>
|
||||
توقيت آخر تحديث للبيانات:
|
||||
<span id="lastUpdatedBadge" class="font-bold text-yellow-400 dir-ltr">جاري التحميل...</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button onclick="location.reload()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm flex items-center gap-2 transition">
|
||||
<i class="fa-solid fa-rotate-right"></i>
|
||||
تحديث الصفحة
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loadingState" class="text-center py-20">
|
||||
<div class="loader"></div>
|
||||
<p class="text-gray-500 mt-4">جاري جلب ملف البيانات (active_drivers_cache.json)...</p>
|
||||
</div>
|
||||
|
||||
<div id="errorState" class="hidden container mx-auto p-6 text-center">
|
||||
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-8 rounded-lg">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl mb-4"></i>
|
||||
<h3 class="font-bold text-lg">تعذر تحميل البيانات</h3>
|
||||
<p class="mt-2">تأكد من وجود ملف <b>active_drivers_cache.json</b> في نفس المجلد.</p>
|
||||
<p class="text-sm mt-2 text-gray-500">ملاحظة تقنية: يجب تشغيل هذا الملف عبر سيرفر محلي (Localhost) ليعمل بشكل صحيح.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mainContent" class="container mx-auto p-6 hidden animate-fade-in">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-blue-500">
|
||||
<p class="text-gray-500 text-xs font-bold">إجمالي السائقين</p>
|
||||
<h3 class="text-2xl font-bold text-blue-700" id="statTotal">0</h3>
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-yellow-500">
|
||||
<p class="text-gray-500 text-xs font-bold">العمالقة (النخبة)</p>
|
||||
<h3 class="text-2xl font-bold text-yellow-600" id="statElite">0</h3>
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-red-500">
|
||||
<p class="text-gray-500 text-xs font-bold">الخاملون (للاتصال)</p>
|
||||
<h3 class="text-2xl font-bold text-red-600" id="statInactive">0</h3>
|
||||
</div>
|
||||
<div class="bg-white p-4 rounded-xl shadow-sm border-r-4 border-green-500">
|
||||
<p class="text-gray-500 text-xs font-bold">أعلى نشاط مسجل</p>
|
||||
<h3 class="text-xl font-bold text-green-600 dir-ltr" id="statMaxTime">0</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-100">
|
||||
<h3 class="font-bold text-sm mb-4 text-gray-700">توزيع حالة السائقين</h3>
|
||||
<div class="h-48">
|
||||
<canvas id="driversChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-100 lg:col-span-2 flex flex-col">
|
||||
<h3 class="font-bold text-sm mb-4 text-gray-700">تصفية وفرز القائمة</h3>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mb-4" id="filterButtons">
|
||||
<button onclick="filterData('all')" class="px-3 py-1.5 rounded-md bg-slate-800 text-white text-xs hover:bg-slate-700 ring-2 ring-offset-1 ring-slate-800 active-filter">الكل</button>
|
||||
<button onclick="filterData('elite')" class="px-3 py-1.5 rounded-md bg-yellow-500 text-white text-xs hover:bg-yellow-600">النخبة (+50س)</button>
|
||||
<button onclick="filterData('stable')" class="px-3 py-1.5 rounded-md bg-green-500 text-white text-xs hover:bg-green-600">مستقرون</button>
|
||||
<button onclick="filterData('experimental')" class="px-3 py-1.5 rounded-md bg-blue-400 text-white text-xs hover:bg-blue-500">تجريبيون</button>
|
||||
<button onclick="filterData('inactive')" class="px-3 py-1.5 rounded-md bg-red-500 text-white text-xs hover:bg-red-600">خاملون (أولوية)</button>
|
||||
</div>
|
||||
|
||||
<div class="relative mt-auto w-full">
|
||||
<input type="text" id="searchInput" placeholder="ابحث عن اسم أو رقم..."
|
||||
class="w-full p-2.5 pr-10 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none">
|
||||
<i class="fa-solid fa-magnifying-glass absolute top-3 right-3 text-gray-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm text-right">
|
||||
<thead class="bg-gray-50 text-gray-600 border-b">
|
||||
<tr>
|
||||
<th class="px-6 py-3 font-bold">السائق</th>
|
||||
<th class="px-6 py-3 font-bold">الهاتف</th>
|
||||
<th class="px-6 py-3 font-bold cursor-pointer hover:text-blue-600" onclick="sortData('minutes')">
|
||||
الأداء (ساعات) <i class="fa-solid fa-sort"></i>
|
||||
</th>
|
||||
<th class="px-6 py-3 font-bold">الحالة</th>
|
||||
<th class="px-6 py-3 text-center font-bold">اتصال</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody" class="divide-y divide-gray-100">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-50 text-xs text-gray-500 flex justify-between">
|
||||
<span id="showingCount"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const JSON_FILE_NAME = 'active_drivers_cache.json';
|
||||
let rawData = [];
|
||||
let filteredData = [];
|
||||
let chartInstance = null;
|
||||
|
||||
// عند تحميل الصفحة
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
fetchData();
|
||||
});
|
||||
|
||||
// دالة جلب البيانات تلقائياً
|
||||
async function fetchData() {
|
||||
try {
|
||||
// إضافة طابع زمني لمنع الكاش وضمان جلب أحدث ملف
|
||||
const timestamp = new Date().getTime();
|
||||
const response = await fetch(`${JSON_FILE_NAME}?t=${timestamp}`);
|
||||
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
|
||||
const json = await response.json();
|
||||
|
||||
// إخفاء اللودر وإظهار المحتوى
|
||||
document.getElementById('loadingState').classList.add('hidden');
|
||||
document.getElementById('mainContent').classList.remove('hidden');
|
||||
|
||||
// تحديث تاريخ آخر تحديث في الهيدر
|
||||
if(json.last_updated) {
|
||||
document.getElementById('lastUpdatedBadge').innerText = json.last_updated;
|
||||
}
|
||||
|
||||
processData(json);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
document.getElementById('loadingState').classList.add('hidden');
|
||||
document.getElementById('errorState').classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function processData(json) {
|
||||
rawData = json.data.map(driver => {
|
||||
const timeStr = driver.active_time || "0 ساعة و 0 دقيقة";
|
||||
const hoursMatch = timeStr.match(/(\d+)\s*ساعة/);
|
||||
const minsMatch = timeStr.match(/(\d+)\s*دقيقة/);
|
||||
|
||||
const hours = hoursMatch ? parseInt(hoursMatch[1]) : 0;
|
||||
const mins = minsMatch ? parseInt(minsMatch[1]) : 0;
|
||||
const totalMinutes = (hours * 60) + mins;
|
||||
|
||||
// تصنيف السائقين
|
||||
let category = 'inactive';
|
||||
let catLabel = 'خامل';
|
||||
let catColor = 'bg-red-100 text-red-700 border border-red-200';
|
||||
|
||||
if (totalMinutes >= 3000) { // 50 hours
|
||||
category = 'elite';
|
||||
catLabel = 'نخبة';
|
||||
catColor = 'bg-yellow-100 text-yellow-700 border border-yellow-200';
|
||||
} else if (totalMinutes >= 1200) { // 20 hours
|
||||
category = 'stable';
|
||||
catLabel = 'مستقر';
|
||||
catColor = 'bg-green-100 text-green-700 border border-green-200';
|
||||
} else if (totalMinutes >= 300) { // 5 hours
|
||||
category = 'experimental';
|
||||
catLabel = 'تجريبي';
|
||||
catColor = 'bg-blue-100 text-blue-700 border border-blue-200';
|
||||
}
|
||||
|
||||
return { ...driver, totalMinutes, hours, mins, category, catLabel, catColor };
|
||||
});
|
||||
|
||||
// الترتيب الافتراضي: حسب الأكثر نشاطاً
|
||||
rawData.sort((a, b) => b.totalMinutes - a.totalMinutes);
|
||||
|
||||
updateStats();
|
||||
filterData('all');
|
||||
}
|
||||
|
||||
function updateStats() {
|
||||
const total = rawData.length;
|
||||
const elite = rawData.filter(d => d.category === 'elite').length;
|
||||
const inactive = rawData.filter(d => d.category === 'inactive').length;
|
||||
|
||||
document.getElementById('statTotal').innerText = total;
|
||||
document.getElementById('statElite').innerText = elite;
|
||||
document.getElementById('statInactive').innerText = inactive;
|
||||
|
||||
if(rawData.length > 0) {
|
||||
document.getElementById('statMaxTime').innerText = rawData[0].active_time;
|
||||
}
|
||||
|
||||
// Chart.js
|
||||
const ctx = document.getElementById('driversChart').getContext('2d');
|
||||
if (chartInstance) chartInstance.destroy();
|
||||
|
||||
const stable = rawData.filter(d => d.category === 'stable').length;
|
||||
const exp = rawData.filter(d => d.category === 'experimental').length;
|
||||
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['النخبة', 'مستقر', 'تجريبي', 'خامل'],
|
||||
datasets: [{
|
||||
data: [elite, stable, exp, inactive],
|
||||
backgroundColor: ['#EAB308', '#22C55E', '#60A5FA', '#EF4444'],
|
||||
borderWidth: 0
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'left', labels: { font: { family: 'Tajawal', size: 10 }, boxWidth: 10 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function filterData(type) {
|
||||
// Update UI Buttons
|
||||
document.querySelectorAll('#filterButtons button').forEach(btn => {
|
||||
btn.classList.remove('ring-2', 'ring-offset-1', 'ring-slate-800');
|
||||
if(btn.getAttribute('onclick').includes(`'${type}'`)) {
|
||||
btn.classList.add('ring-2', 'ring-offset-1', 'ring-slate-800');
|
||||
}
|
||||
});
|
||||
|
||||
filteredData = type === 'all' ? rawData : rawData.filter(d => d.category === type);
|
||||
|
||||
document.getElementById('searchInput').value = '';
|
||||
renderTable(filteredData);
|
||||
}
|
||||
|
||||
// البحث
|
||||
document.getElementById('searchInput').addEventListener('input', (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
const currentData = filteredData;
|
||||
// ملاحظة: البحث يتم داخل الفئة المختارة حالياً
|
||||
const searchResults = filteredData.filter(driver =>
|
||||
(driver.name_arabic && driver.name_arabic.toLowerCase().includes(term)) ||
|
||||
(driver.phone && driver.phone.includes(term))
|
||||
);
|
||||
renderTable(searchResults);
|
||||
});
|
||||
|
||||
function renderTable(data) {
|
||||
const tbody = document.getElementById('tableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="5" class="text-center py-8 text-gray-400">لا توجد بيانات</td></tr>`;
|
||||
document.getElementById('showingCount').innerText = '';
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach(driver => {
|
||||
const createdDate = driver.created_at ? driver.created_at.split(' ')[0] : '-';
|
||||
|
||||
const row = `
|
||||
<tr class="hover:bg-slate-50 driver-card bg-white">
|
||||
<td class="px-6 py-3">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold text-slate-700">${driver.name_arabic || 'غير مسمى'}</span>
|
||||
<span class="text-[10px] text-gray-400">انضم: ${createdDate}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-3 font-mono text-xs text-gray-600 dir-ltr text-right select-all">
|
||||
${driver.phone}
|
||||
</td>
|
||||
<td class="px-6 py-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="w-full bg-gray-100 rounded-full h-2 overflow-hidden">
|
||||
<div class="h-2 ${driver.category === 'elite' ? 'bg-yellow-400' : driver.category === 'inactive' ? 'bg-red-400' : 'bg-blue-400'}" style="width: ${Math.min((driver.totalMinutes / 6000) * 100, 100)}%"></div>
|
||||
</div>
|
||||
<span class="text-[10px] text-gray-500 font-bold">${driver.active_time}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-3">
|
||||
<span class="${driver.catColor} px-2 py-0.5 rounded text-[10px] font-bold inline-block min-w-[50px] text-center">
|
||||
${driver.catLabel}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-3 text-center">
|
||||
<div class="flex justify-center gap-2">
|
||||
<a href="https://wa.me/${driver.phone}" target="_blank" class="w-8 h-8 rounded-full bg-green-100 text-green-600 hover:bg-green-600 hover:text-white flex items-center justify-center transition">
|
||||
<i class="fa-brands fa-whatsapp"></i>
|
||||
</a>
|
||||
<a href="tel:${driver.phone}" class="w-8 h-8 rounded-full bg-blue-100 text-blue-600 hover:bg-blue-600 hover:text-white flex items-center justify-center transition">
|
||||
<i class="fa-solid fa-phone"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
tbody.insertAdjacentHTML('beforeend', row);
|
||||
});
|
||||
|
||||
document.getElementById('showingCount').innerText = `عرض ${data.length} من أصل ${rawData.length}`;
|
||||
}
|
||||
|
||||
let sortDir = 'desc';
|
||||
function sortData(key) {
|
||||
sortDir = sortDir === 'desc' ? 'asc' : 'desc';
|
||||
filteredData.sort((a, b) => sortDir === 'asc' ? a.totalMinutes - b.totalMinutes : b.totalMinutes - a.totalMinutes);
|
||||
renderTable(filteredData);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
188
backend/ride/location/get.php
Executable file
188
backend/ride/location/get.php
Executable file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
// get.php (Main Server)
|
||||
|
||||
// 1. تضمين ملف الاتصال والدوال المساعدة (مهم جداً)
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
// ضبط التوقيت (للسجلات فقط، قاعدة البيانات تبقى UTC)
|
||||
// date_default_timezone_set('Asia/Amman');
|
||||
|
||||
try {
|
||||
// ==========================================
|
||||
// 1. استقبال الإحداثيات (دعم الطريقتين)
|
||||
// ==========================================
|
||||
$lat = filterRequest("lat");
|
||||
$lng = filterRequest("lng");
|
||||
|
||||
// دعم Bounds القديم (تحويله لمركز وقطر)
|
||||
if (!$lat || !$lng) {
|
||||
$swLat = filterRequest("southwestLat"); $neLat = filterRequest("northeastLat");
|
||||
$swLon = filterRequest("southwestLon"); $neLon = filterRequest("northeastLon");
|
||||
|
||||
if ($swLat && $neLat && $swLon && $neLon) {
|
||||
$lat = ($swLat + $neLat) / 2;
|
||||
$lng = ($swLon + $neLon) / 2;
|
||||
} else {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. طلب الـ IDs والمواقع من سيرفر اللوكيشن (Redis API)
|
||||
// ==========================================
|
||||
$locationServerUrl = getenv('LOCATION_API_URL');
|
||||
// تأكد من المسار الصحيح للمفتاح على السيرفر الرئيسي
|
||||
$INTERNAL_KEY = trim(file_get_contents(getenv('INTERNAL_SOCKET_KEY_PATH')));
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $locationServerUrl);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'radius' => 5, // 5 كم كافية للعرض
|
||||
'limit' => 50
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$redisDrivers = [];
|
||||
if ($httpCode == 200 && $response) {
|
||||
$json = json_decode($response, true);
|
||||
if (isset($json['status']) && $json['status'] === true) {
|
||||
$redisDrivers = $json['data'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($redisDrivers)) {
|
||||
jsonSuccess([]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// تجهيز خريطة لدمج البيانات لاحقاً (ID => RedisData)
|
||||
$driversMap = [];
|
||||
$driverIds = [];
|
||||
foreach ($redisDrivers as $d) {
|
||||
$driverIds[] = $d['id'];
|
||||
$driversMap[$d['id']] = $d;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. جلب التفاصيل الكاملة من MySQL (مثل الملف القديم تماماً)
|
||||
// ==========================================
|
||||
|
||||
// تجهيز الـ Placeholders
|
||||
$placeholders = implode(',', array_fill(0, count($driverIds), '?'));
|
||||
|
||||
// الاستعلام الشامل (نفس الحقول القديمة)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id,
|
||||
d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
|
||||
cr.make,
|
||||
cr.car_plate,
|
||||
cr.model,
|
||||
cr.color, cr.vin, cr.color_hex,
|
||||
cr.year,
|
||||
cr.vehicle_category_id,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND COALESCE(cr.year, 0) > 2000
|
||||
-- AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql_drivers_info);
|
||||
$stmt->execute($driverIds);
|
||||
$drivers_db = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($drivers_db)) {
|
||||
jsonSuccess([], "No matching drivers in DB");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 4. معالجة البيانات، الدمج، وفك التشفير
|
||||
// ==========================================
|
||||
|
||||
$final_result = [];
|
||||
$serverNow = date('Y-m-d H:i:s');
|
||||
$fieldsToDecrypt = ['phone','email','gender','birthdate','first_name','last_name','token','car_plate','vin'];
|
||||
|
||||
// الهاش الخاص بالإناث (لتحديد النوع لاحقاً إذا لزم الأمر)
|
||||
// $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
|
||||
|
||||
foreach ($drivers_db as $row) {
|
||||
$did = $row['driver_id'];
|
||||
|
||||
// دمج بيانات الموقع الحية من الريدز (أهم خطوة)
|
||||
if (isset($driversMap[$did])) {
|
||||
$redisInfo = $driversMap[$did];
|
||||
$row['latitude'] = $redisInfo['lat'];
|
||||
$row['longitude'] = $redisInfo['lng'];
|
||||
$row['heading'] = $redisInfo['heading'];
|
||||
$row['speed'] = $redisInfo['speed'];
|
||||
// $row['distance'] = $redisInfo['distance']; // إذا أردت إضافتها
|
||||
} else {
|
||||
// حالة نادرة: السائق موجود في الاستعلام ولكن ليس في مصفوفة الريدز (لا يجب أن تحدث)
|
||||
continue;
|
||||
}
|
||||
|
||||
$row['serverNow'] = $serverNow;
|
||||
|
||||
// فك التشفير (Decrypt)
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && $row[$field] !== null && $row[$field] !== '') {
|
||||
try {
|
||||
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||
} catch (Exception $e) {
|
||||
$row[$field] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// حساب العمر
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthdate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthdate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
|
||||
// إضافة نوع السيارة البسيط (اختياري، إذا كان التطبيق يعتمد عليه)
|
||||
/*
|
||||
$type = 'car';
|
||||
if ($row['vehicle_category_id'] == 2) $type = 'bike';
|
||||
elseif ($row['gender'] == 'female') $type = 'lady'; // بعد فك التشفير تكون female
|
||||
$row['type'] = $type;
|
||||
*/
|
||||
|
||||
$final_result[] = $row;
|
||||
}
|
||||
|
||||
// إرجاع النتيجة بنفس الهيكل القديم
|
||||
jsonSuccess($final_result);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
166
backend/ride/location/getBalash.php
Executable file
166
backend/ride/location/getBalash.php
Executable file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
//getBalash.php
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
|
||||
// إعادة استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// ملاحظة: نزيل LIMIT 5 من هنا ونجلب مجموعة أكبر من المرشحين
|
||||
// لأن الترتيب النهائي يعتمد على بيانات من القاعدة الأخرى
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 20; -- نجلب 100 مرشح محتمل للفلترة والترتيب لاحقاً
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة < 2000)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND COALESCE(cr.year, 0) < 2000 -- ⭐ الشرط الخاص بهذا السكريبت
|
||||
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
|
||||
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
|
||||
";
|
||||
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
// الآن بعد أن دمجنا كل البيانات، يمكننا تطبيق الترتيب المعقد
|
||||
usort($final_results, function ($a, $b) {
|
||||
// الترتيب الأول: حسب التقييم (تنازلي)
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
// الترتيب الثاني: حسب عدد التقييمات (تنازلي)
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
// الترتيب الثالث: حسب حداثة الموقع (تنازلي)
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
// وأخيراً، نأخذ أفضل 5 نتائج فقط
|
||||
$limited_results = array_slice($final_results, 0, 5);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No cars matching the specific criteria (year < 2000) found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
160
backend/ride/location/getCarsLocationByPassengerVan.php
Executable file
160
backend/ride/location/getCarsLocationByPassengerVan.php
Executable file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
|
||||
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 100; -- نجلب 100 مرشح محتمل
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (سيارات كهربائية فقط)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year, cr.fuel,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND cr.make = 'Van'or cr.model='Van'
|
||||
|
||||
";
|
||||
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
usort($final_results, function ($a, $b) {
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
// وأخيراً، نأخذ أفضل 10 نتائج فقط
|
||||
$limited_results = array_slice($final_results, 0, 10);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No electric cars matching the criteria found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
170
backend/ride/location/getComfort.php
Executable file
170
backend/ride/location/getComfort.php
Executable file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
//getComfort.php
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
|
||||
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 100; -- نجلب 100 مرشح محتمل
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة > 2017)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
|
||||
FROM driver d
|
||||
-- 1. تغيير LEFT JOIN إلى INNER JOIN لضمان عدم جلب سائق إلا إذا كانت بيانات سيارته مطابقة تماماً
|
||||
INNER JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
-- 2. الفلترة الصارمة للسنة
|
||||
AND cr.year IS NOT NULL
|
||||
AND TRIM(cr.year) != ''
|
||||
AND CAST(TRIM(cr.year) AS UNSIGNED) > 2017
|
||||
|
||||
|
||||
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
|
||||
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
|
||||
";
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
// الآن بعد أن دمجنا كل البيانات، يمكننا تطبيق الترتيب المعقد
|
||||
usort($final_results, function ($a, $b) {
|
||||
// الترتيب الأول: حسب التقييم (تنازلي)
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
// الترتيب الثاني: حسب عدد التقييمات (تنازلي)
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
// الترتيب الثالث: حسب حداثة الموقع (تنازلي)
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
// وأخيراً، نأخذ أفضل 5 نتائج فقط
|
||||
$limited_results = array_slice($final_results, 0, 5);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No cars matching the specific criteria (year > 2017) found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
159
backend/ride/location/getDelivery.php
Executable file
159
backend/ride/location/getDelivery.php
Executable file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
//getDelivery.php
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
|
||||
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 100; -- نجلب 100 مرشح محتمل
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (دراجات فقط)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND (cr.make LIKE '%دراج%' OR cr.model LIKE '%دراج%') -- ⭐ الشرط الخاص بهذا السكريبت
|
||||
";
|
||||
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
usort($final_results, function ($a, $b) {
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
// وأخيراً، نأخذ أفضل 10 نتائج فقط
|
||||
$limited_results = array_slice($final_results, 0, 10);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No motorcycles matching the criteria found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name','maritalStatus', 'token','make','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
// تأكد من أن ملف connect.php يقوم بتهيئة اتصالين:
|
||||
// $con -> يتصل بقاعدة البيانات الأساسية (driver, CarRegistration)
|
||||
// $con_tracking -> يتصل بقاعدة بيانات التتبع (car_locations)
|
||||
// وأنه يحتوي على كائن التشفير $encryptionHelper
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$con_tracking = Database::get('tracking');
|
||||
// $con = Database::get('main'); // Add this just in case as well, to be safe.
|
||||
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
if ($driver_id === false || empty($driver_id)) {
|
||||
jsonError("Invalid driver_id provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب آخر موقع للسائق من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
$sql_location = "SELECT
|
||||
driver_id,
|
||||
latitude,
|
||||
longitude,
|
||||
heading,
|
||||
speed,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
car_locations
|
||||
WHERE
|
||||
driver_id = ?
|
||||
ORDER BY
|
||||
updated_at DESC
|
||||
LIMIT 1";
|
||||
|
||||
$stmt_location = $con_tracking->prepare($sql_location);
|
||||
$stmt_location->execute([$driver_id]);
|
||||
$location_data = $stmt_location->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// إذا لم نجد أي موقع، لا داعي لإكمال البحث
|
||||
if (empty($location_data)) {
|
||||
jsonError("No car locations found");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: جلب البيانات الثابتة للسائق من القاعدة الأساسية
|
||||
// =================================================================
|
||||
$sql_driver_info = "SELECT
|
||||
d.gender,
|
||||
cr.model
|
||||
FROM
|
||||
driver d
|
||||
LEFT JOIN CarRegistration cr ON d.id = cr.driverID
|
||||
WHERE
|
||||
d.id = ?";
|
||||
|
||||
$stmt_driver_info = $con->prepare($sql_driver_info);
|
||||
$stmt_driver_info->execute([$driver_id]);
|
||||
// نستخدم fetch وليس fetchAll لأننا نتوقع سائق واحد فقط
|
||||
$driver_info = $stmt_driver_info->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2.5: فك تشفير الجندر (New Step)
|
||||
// =================================================================
|
||||
if (!empty($driver_info)) {
|
||||
// التحقق من وجود قيمة في حقل الجندر قبل فك تشفيرها
|
||||
if (isset($driver_info['gender']) && !empty($driver_info['gender'])) {
|
||||
$driver_info['gender'] = $encryptionHelper->decryptData($driver_info['gender']);
|
||||
}
|
||||
} else {
|
||||
$driver_info = []; // اجعله مصفوفة فارغة لتجنب خطأ في الدمج
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: دمج النتائج (Application-Side Join)
|
||||
// =================================================================
|
||||
|
||||
// دمج بيانات الموقع مع بيانات السائق (التي تم فك تشفيرها الآن)
|
||||
$final_result = array_merge($location_data, $driver_info);
|
||||
|
||||
// إرجاع النتيجة داخل مصفوفة كما في السكربت الأصلي
|
||||
jsonSuccess([$final_result]);
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
130
backend/ride/location/getDriverTimeOnline.php
Executable file
130
backend/ride/location/getDriverTimeOnline.php
Executable file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
// getDriverTimeOnline.php
|
||||
// الغرض: توليد تقرير نشاط السائقين لآخر 10 أيام بناءً على الملخص اليومي
|
||||
|
||||
require_once __DIR__ . '/../../get_connect.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// إعدادات التقرير
|
||||
$daysToLookBack = 10; // عدد الأيام في الماضي
|
||||
$jsonFileName = 'active_drivers_cache.json';
|
||||
$savePath = __DIR__ . '/' . $jsonFileName;
|
||||
|
||||
try {
|
||||
// =================================================================
|
||||
// 1. جلب البيانات من جدول الملخصات (سيرفر اللوكيشن) ⚡
|
||||
// =================================================================
|
||||
// نجمع الثواني لكل سائق خلال الفترة المحددة
|
||||
|
||||
$sql_summary = "
|
||||
SELECT
|
||||
driver_id,
|
||||
SUM(total_seconds) as grand_total_seconds,
|
||||
COUNT(DISTINCT date) as days_worked -- (اختياري) معرفة عدد أيام العمل الفعلية
|
||||
FROM driver_daily_summary
|
||||
WHERE date >= DATE_SUB(CURDATE(), INTERVAL ? DAY)
|
||||
GROUP BY driver_id
|
||||
HAVING grand_total_seconds > 60 -- (اختياري) تجاهل من عمل أقل من دقيقة
|
||||
ORDER BY grand_total_seconds DESC
|
||||
";
|
||||
|
||||
$stmt = $con_tracking->prepare($sql_summary);
|
||||
$stmt->execute([$daysToLookBack]);
|
||||
$summary_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($summary_data)) {
|
||||
// حفظ ملف فارغ وإنهاء
|
||||
saveJsonFile($savePath, ["last_updated" => date('Y-m-d H:i:s'), "data" => []]);
|
||||
printSuccess("No active drivers found in summary.", $savePath);
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 2. جلب تفاصيل السائقين (الأسماء) من السيرفر الرئيسي 📝
|
||||
// =================================================================
|
||||
|
||||
// استخراج الـ IDs
|
||||
$driver_ids = array_column($summary_data, 'driver_id');
|
||||
|
||||
// تجهيز الـ Placeholders (?,?,?)
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
$sql_drivers = "SELECT id, name_arabic, phone, created_at FROM driver WHERE id IN ($placeholders)";
|
||||
|
||||
$stmt_d = $con->prepare($sql_drivers);
|
||||
$stmt_d->execute($driver_ids);
|
||||
$drivers_raw = $stmt_d->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل البيانات لـ Map لسرعة الدمج: [id => data]
|
||||
$drivers_map = [];
|
||||
foreach ($drivers_raw as $d) {
|
||||
$drivers_map[$d['id']] = $d;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 3. دمج البيانات وفك التشفير وتنسيق الوقت 🔄
|
||||
// =================================================================
|
||||
|
||||
$final_report = [];
|
||||
$fieldsToDecrypt = ['phone', 'name_arabic']; // الحقول المشفرة
|
||||
|
||||
foreach ($summary_data as $row) {
|
||||
$did = $row['driver_id'];
|
||||
$seconds = $row['grand_total_seconds'];
|
||||
|
||||
// البيانات الشخصية
|
||||
$personalData = isset($drivers_map[$did]) ? $drivers_map[$did] : ['name_arabic' => 'Unknown', 'phone' => ''];
|
||||
|
||||
// فك التشفير
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (!empty($personalData[$field])) {
|
||||
try {
|
||||
$personalData[$field] = $encryptionHelper->decryptData($personalData[$field]);
|
||||
} catch (Exception $e) {
|
||||
// ابقها مشفرة أو ضع قيمة افتراضية عند الفشل
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// تنسيق الوقت (مقروء للبشر)
|
||||
$hours = floor($seconds / 3600);
|
||||
$minutes = floor(($seconds % 3600) / 60);
|
||||
$human_time = sprintf("%d ساعة و %d دقيقة", $hours, $minutes);
|
||||
|
||||
// بناء الصف النهائي
|
||||
$final_report[] = [
|
||||
'driver_id' => $did,
|
||||
'name' => $personalData['name_arabic'],
|
||||
'phone' => $personalData['phone'],
|
||||
'join_date' => $personalData['created_at'], // تاريخ انضمام السائق
|
||||
'total_seconds' => $seconds,
|
||||
'active_time' => $human_time,
|
||||
'days_active' => $row['days_worked'] // عدد الأيام التي عمل فيها خلال الـ 10 أيام
|
||||
];
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// 4. الحفظ والنشر 💾
|
||||
// =================================================================
|
||||
|
||||
$output = [
|
||||
"last_updated" => date('Y-m-d H:i:s'),
|
||||
"period_days" => $daysToLookBack,
|
||||
"total_drivers" => count($final_report),
|
||||
"data" => $final_report
|
||||
];
|
||||
|
||||
saveJsonFile($savePath, $output);
|
||||
printSuccess("Report generated based on Daily Summary.", $savePath);
|
||||
|
||||
} catch (Exception $e) {
|
||||
jsonError("Error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// --- دوال مساعدة ---
|
||||
function saveJsonFile($path, $data) {
|
||||
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
file_put_contents($path, $json);
|
||||
}
|
||||
?>
|
||||
161
backend/ride/location/getElectric.php
Executable file
161
backend/ride/location/getElectric.php
Executable file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
//getElectric.php
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
|
||||
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 100; -- نجلب 100 مرشح محتمل
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (سيارات كهربائية فقط)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year, cr.fuel,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(*) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND cr.fuel = 'كهربائي'
|
||||
AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
|
||||
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
|
||||
";
|
||||
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
usort($final_results, function ($a, $b) {
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
// وأخيراً، نأخذ أفضل 10 نتائج فقط
|
||||
$limited_results = array_slice($final_results, 0, 10);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No electric cars matching the criteria found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
160
backend/ride/location/getFemalDriver.php
Executable file
160
backend/ride/location/getFemalDriver.php
Executable file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
//getFemalDriver.php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
|
||||
// استخدام التحسين الجغرافي ST_CONTAINS لأنه أسرع بمراحل
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// جلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 100; -- نجلب 100 مرشح محتمل
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (سائقات إناث فقط)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
|
||||
dt.token,
|
||||
COALESCE(AVG(rd.rating), 0) AS ratingDriver,
|
||||
COUNT(rd.id) AS ratingCount
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN ratingDriver rd ON rd.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND d.gender = 'Female' -- ⭐ الشرط الخاص بهذا السكريبت
|
||||
AND (cr.make NOT LIKE '%دراجة%' AND cr.model NOT LIKE '%دراجة%')
|
||||
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
|
||||
GROUP BY d.id -- تجميع النتائج حسب السائق لحساب التقييم بشكل صحيح
|
||||
";
|
||||
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
// ندمج فقط السائقين الذين طابقوا شروطنا في الاستعلام الثاني (أنثى)
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
usort($final_results, function ($a, $b) {
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
// وأخيراً، نأخذ أفضل 10 نتائج فقط
|
||||
$limited_results = array_slice($final_results, 0, 10);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No female drivers matching the criteria found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[
|
||||
$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
29
backend/ride/location/getLatestLocationPassenger.php
Normal file
29
backend/ride/location/getLatestLocationPassenger.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$rideId = filterRequest("rideId");
|
||||
|
||||
$sql = "SELECT
|
||||
*
|
||||
FROM
|
||||
`passengerlocation` pl
|
||||
WHERE
|
||||
pl.rideId = :rideId
|
||||
ORDER BY
|
||||
pl.createdAt
|
||||
DESC
|
||||
LIMIT 1";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute([':rideId' => $rideId]);
|
||||
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($car_locations) {
|
||||
// Print the car location data as JSON
|
||||
jsonSuccess($data = $car_locations);
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError($message = "No car locations found");
|
||||
}
|
||||
|
||||
?>
|
||||
88
backend/ride/location/getLocationParents.php
Normal file
88
backend/ride/location/getLocationParents.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// تأكد من أن ملف connect.php يقوم بتهيئة اتصالين:
|
||||
// $con -> يتصل بقاعدة البيانات الأساسية (driver, CarRegistration)
|
||||
// $con_tracking -> يتصل بقاعدة بيانات التتبع (car_locations)
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
if ($driver_id === false || empty($driver_id)) {
|
||||
jsonError("Invalid driver_id provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب آخر موقع للسائق من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
// هذا الاستعلام يعمل على قاعدة بيانات التتبع السريعة
|
||||
// (ملاحظة: تم التغيير إلى ORDER BY updated_at لجلب آخر تحديث)
|
||||
$sql_location = "SELECT
|
||||
id,
|
||||
driver_id,
|
||||
latitude,
|
||||
longitude,
|
||||
heading,
|
||||
speed,
|
||||
status,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
car_locations
|
||||
WHERE
|
||||
driver_id = ?
|
||||
ORDER BY
|
||||
updated_at DESC
|
||||
LIMIT 1";
|
||||
|
||||
$stmt_location = $con_tracking->prepare($sql_location);
|
||||
$stmt_location->execute([$driver_id]);
|
||||
$location_data = $stmt_location->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// إذا لم نجد أي موقع، لا داعي لإكمال البحث
|
||||
if (empty($location_data)) {
|
||||
jsonError("No car locations found");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: جلب البيانات الثابتة للسائق من القاعدة الأساسية
|
||||
// =================================================================
|
||||
// هذا الاستعلام يعمل على قاعدة البيانات الأساسية الهادئة
|
||||
// (ملاحظة: تم إصلاح الخطأ في جملة JOIN)
|
||||
$sql_driver_info = "SELECT
|
||||
d.gender,
|
||||
cr.model
|
||||
FROM
|
||||
driver d
|
||||
LEFT JOIN CarRegistration cr ON d.id = cr.driverID
|
||||
WHERE
|
||||
d.id = ?";
|
||||
|
||||
$stmt_driver_info = $con->prepare($sql_driver_info);
|
||||
$stmt_driver_info->execute([$driver_id]);
|
||||
$driver_info = $stmt_driver_info->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: دمج النتائج (Application-Side Join)
|
||||
// =================================================================
|
||||
|
||||
// دمج بيانات الموقع مع بيانات السائق
|
||||
if (empty($driver_info)) {
|
||||
$driver_info = []; // اجعله مصفوفة فارغة لتجنب خطأ في الدمج
|
||||
}
|
||||
|
||||
$final_result = array_merge($location_data, $driver_info);
|
||||
|
||||
// السكربت الأصلي كان يستخدم fetchAll، لذا كان يرجع مصفوفة بداخلها عنصر واحد
|
||||
// [ [ ... بيانات ... ] ]
|
||||
// سنحافظ على نفس البنية لإرجاع البيانات
|
||||
jsonSuccess([$final_result]);
|
||||
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
112
backend/ride/location/getPinkBike.php
Executable file
112
backend/ride/location/getPinkBike.php
Executable file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
cl.driver_id,
|
||||
cl.latitude,
|
||||
cl.longitude,
|
||||
cl.heading,
|
||||
cl.speed,
|
||||
cl.status,
|
||||
cl.created_at,
|
||||
cl.updated_at,
|
||||
d.phone,
|
||||
d.email,
|
||||
d.birthdate,
|
||||
d.first_name,
|
||||
d.last_name,
|
||||
d.gender,
|
||||
d.maritalStatus,
|
||||
cr.make,
|
||||
cr.car_plate,
|
||||
cr.model,
|
||||
cr.color,
|
||||
cr.vin,
|
||||
cr.color_hex,
|
||||
cr.year,
|
||||
dt.token,
|
||||
'' AS age,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
rdAvg.ratingCount
|
||||
FROM
|
||||
car_locations cl
|
||||
LEFT JOIN driver d ON d.id = cl.driver_id
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = cl.driver_id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = cl.driver_id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = cl.driver_id
|
||||
WHERE
|
||||
cl.latitude BETWEEN :southwestLat AND :northeastLat
|
||||
AND cl.longitude BETWEEN :southwestLon AND :northeastLon
|
||||
AND cl.status = 'off'
|
||||
AND cl.updated_at >= NOW() - INTERVAL 5 SECOND
|
||||
AND (cr.make LIKE '%دراجة%' OR cr.model LIKE '%دراجة%')
|
||||
GROUP BY cl.driver_id
|
||||
ORDER BY ratingDriver DESC, cl.updated_at DESC
|
||||
LIMIT 10;
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':southwestLat', $southwestLat);
|
||||
$stmt->bindParam(':southwestLon', $southwestLon);
|
||||
$stmt->bindParam(':northeastLat', $northeastLat);
|
||||
$stmt->bindParam(':northeastLon', $northeastLon);
|
||||
$stmt->execute();
|
||||
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($rows) {
|
||||
$fieldsToDecrypt = [
|
||||
'phone', 'email', 'gender', 'birthdate',
|
||||
'first_name', 'last_name', 'maritalStatus', 'token',
|
||||
'make', 'car_plate', 'vin'
|
||||
];
|
||||
|
||||
$filteredRows = [];
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field])) {
|
||||
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
// فلترة حسب الجنس
|
||||
if (strtolower($row['gender']) !== 'female') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// حساب العمر
|
||||
if (!empty($row['birthdate'])) {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
|
||||
$filteredRows[] = $row;
|
||||
}
|
||||
|
||||
jsonSuccess($filteredRows);
|
||||
} else {
|
||||
jsonError("No car locations found");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
}
|
||||
66
backend/ride/location/getRidesDriverByDay.php
Executable file
66
backend/ride/location/getRidesDriverByDay.php
Executable file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$current_month = date('m');
|
||||
$current_year = date('Y');
|
||||
|
||||
// Get the first and last days of the current month.
|
||||
$first_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-01'));
|
||||
$last_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-' . cal_days_in_month(CAL_GREGORIAN, $current_month, $current_year)));
|
||||
|
||||
// Create a SQL query to select the total duration for the driver for each day in the current month.
|
||||
$sql = "SELECT
|
||||
DATE(`ride`.created_at) AS day,
|
||||
COUNT(`ride`.`id`) AS countRide,
|
||||
SUM(`ride`.`price`) AS pricePerDay,
|
||||
(
|
||||
SELECT
|
||||
SUM(`ride`.`price`)
|
||||
FROM
|
||||
`ride`
|
||||
WHERE
|
||||
`ride`.`driver_id` = :driver_id_total AND `ride`.`created_at` >= :first_day_total AND `ride`.created_at < :last_day_total AND `ride`.`status` = 'Finished'
|
||||
) AS totalPrice,
|
||||
(
|
||||
SELECT
|
||||
COUNT(`ride`.`id`)
|
||||
FROM
|
||||
`ride`
|
||||
WHERE
|
||||
`ride`.`driver_id` = :driver_id_count AND `ride`.`created_at` >= :first_day_count AND `ride`.created_at < :last_day_count AND `ride`.`status` = 'Finished'
|
||||
) AS totalCount
|
||||
FROM
|
||||
`ride`
|
||||
WHERE
|
||||
`ride`.`driver_id` = :driver_id_main AND `ride`.`created_at` >= :first_day_main AND `ride`.created_at < :last_day_main AND `ride`.`status` = 'Finished'
|
||||
GROUP BY
|
||||
day
|
||||
ORDER BY
|
||||
day ASC;";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// Bind each parameter uniquely
|
||||
$stmt->bindParam(':driver_id_total', $driver_id, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':first_day_total', $first_day_of_month, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':last_day_total', $last_day_of_month, PDO::PARAM_STR);
|
||||
|
||||
$stmt->bindParam(':driver_id_count', $driver_id, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':first_day_count', $first_day_of_month, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':last_day_count', $last_day_of_month, PDO::PARAM_STR);
|
||||
|
||||
$stmt->bindParam(':driver_id_main', $driver_id, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':first_day_main', $first_day_of_month, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':last_day_main', $last_day_of_month, PDO::PARAM_STR);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($car_locations) {
|
||||
// Print the car location data as JSON
|
||||
jsonSuccess($data = $car_locations);
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError($message = "No car locations found");
|
||||
}
|
||||
?>
|
||||
157
backend/ride/location/getSpeed.php
Executable file
157
backend/ride/location/getSpeed.php
Executable file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
|
||||
//getSpeed.php
|
||||
try {
|
||||
// 1) قراءة والتحقق من الإحداثيات
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
if ($southwestLat === false || $southwestLon === false || $northeastLat === false || $northeastLon === false) {
|
||||
jsonError("Invalid coordinates provided");
|
||||
exit;
|
||||
}
|
||||
|
||||
$freshSeconds = 180; // 3 دقائق
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
|
||||
// =================================================================
|
||||
$boundingBoxWKT = sprintf(
|
||||
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
|
||||
$southwestLon, $southwestLat,
|
||||
$northeastLon, $southwestLat,
|
||||
$northeastLon, $northeastLat,
|
||||
$southwestLon, $northeastLat,
|
||||
$southwestLon, $southwestLat
|
||||
);
|
||||
|
||||
// نجلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
|
||||
$sql_locations = "
|
||||
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
|
||||
FROM car_locations
|
||||
WHERE
|
||||
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
|
||||
AND status = 'off'
|
||||
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 100; -- نجلب 100 مرشح محتمل
|
||||
";
|
||||
|
||||
$stmt_locations = $con_tracking->prepare($sql_locations);
|
||||
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
|
||||
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
|
||||
$stmt_locations->execute();
|
||||
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$locations) {
|
||||
jsonError("No car locations found in the specified area.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 2: تجميع معرفات السائقين (driver_id)
|
||||
// =================================================================
|
||||
$driver_ids = array_column($locations, 'driver_id');
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
|
||||
// =================================================================
|
||||
$drivers_info = [];
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
|
||||
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة > 2000)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
|
||||
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
|
||||
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
-- AND COALESCE(cr.year, 0) > 2000 -- ⭐ الشرط الخاص بهذا السكريبت
|
||||
-- AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
|
||||
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
|
||||
";
|
||||
|
||||
$stmt_drivers_info = $con->prepare($sql_drivers_info);
|
||||
$stmt_drivers_info->execute($driver_ids);
|
||||
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
|
||||
foreach ($drivers_info_raw as $driver) {
|
||||
$drivers_info[$driver['driver_id']] = $driver;
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 4: دمج النتائج في PHP
|
||||
// =================================================================
|
||||
$final_results = [];
|
||||
foreach ($locations as $location) {
|
||||
$driver_id = $location['driver_id'];
|
||||
if (isset($drivers_info[$driver_id])) {
|
||||
$final_results[] = array_merge($location, $drivers_info[$driver_id]);
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
|
||||
// =================================================================
|
||||
usort($final_results, function ($a, $b) {
|
||||
if ($a['ratingDriver'] != $b['ratingDriver']) {
|
||||
return $b['ratingDriver'] <=> $a['ratingDriver'];
|
||||
}
|
||||
if ($a['ratingCount'] != $b['ratingCount']) {
|
||||
return $b['ratingCount'] <=> $a['ratingCount'];
|
||||
}
|
||||
return strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
|
||||
});
|
||||
|
||||
$limited_results = array_slice($final_results, 0, 10);
|
||||
|
||||
if (empty($limited_results)) {
|
||||
jsonError("No cars matching the specific criteria (year > 2000) found.");
|
||||
exit;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
|
||||
// =================================================================
|
||||
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
|
||||
foreach ($limited_results as &$row) {
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && !empty($row[$field])) {
|
||||
try { $row[$field] = $encryptionHelper->decryptData($row[$field]); }
|
||||
catch (Exception $e) { $row[$field] = null; }
|
||||
}
|
||||
}
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthDate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthDate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
jsonSuccess($limited_results);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
44
backend/ride/location/getTotalDriverDuration.php
Executable file
44
backend/ride/location/getTotalDriverDuration.php
Executable file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
$current_month = date('m');
|
||||
$current_year = date('Y');
|
||||
|
||||
// Get the first and last days of the current month.
|
||||
$first_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-01'));
|
||||
$last_day_of_month = date('Y-m-d', strtotime($current_year . '-' . $current_month . '-' . cal_days_in_month(CAL_GREGORIAN, $current_month, $current_year)));
|
||||
|
||||
// Create a SQL query to select the total duration for the driver for each day in the current month.
|
||||
$sql = "SELECT
|
||||
DATE(created_at) AS day,
|
||||
SEC_TO_TIME(COUNT(*) * 60) AS total_duration
|
||||
FROM
|
||||
car_tracks
|
||||
WHERE
|
||||
car_tracks.driver_id = :driver_id
|
||||
AND car_tracks.created_at >= :first_day_of_month
|
||||
AND car_tracks.created_at < :last_day_of_month
|
||||
GROUP BY
|
||||
day
|
||||
ORDER BY
|
||||
day ASC;";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':driver_id', $driver_id, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':first_day_of_month', $first_day_of_month, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':last_day_of_month', $last_day_of_month, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
|
||||
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($car_locations) {
|
||||
// Print the car location data as JSON
|
||||
jsonSuccess($data = $car_locations);
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError($message = "No car locations found");
|
||||
}
|
||||
|
||||
?>
|
||||
31
backend/ride/location/getTotalDriverDurationToday.php
Normal file
31
backend/ride/location/getTotalDriverDurationToday.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$driver_id = filterRequest("driver_id");
|
||||
|
||||
// Get the current date.
|
||||
$current_date = date('Y-m-d');
|
||||
|
||||
// Create a SQL query to select the total duration for the driver today.
|
||||
$sql = "SELECT
|
||||
SEC_TO_TIME(COUNT(*) * 60) AS total_duration
|
||||
FROM
|
||||
car_tracks
|
||||
WHERE
|
||||
car_tracks.driver_id = '$driver_id'
|
||||
AND car_tracks.created_at >= '$current_date'
|
||||
AND car_tracks.created_at < DATE_ADD('$current_date', INTERVAL 1 DAY);";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->execute();
|
||||
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($car_locations) {
|
||||
// Print the car location data as JSON
|
||||
jsonSuccess($car_locations);
|
||||
} else {
|
||||
// Print a failure message
|
||||
jsonError($message = "No car locations found");
|
||||
}
|
||||
|
||||
?>
|
||||
127
backend/ride/location/getUpdatedLocationForAdmin.php
Executable file
127
backend/ride/location/getUpdatedLocationForAdmin.php
Executable file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
// =================================================================
|
||||
// ملف: getUpdatedLocationForAdmin.php
|
||||
// =================================================================
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
// تفعيل إظهار الأخطاء لمعرفة مشكلة الكتابة
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
try {
|
||||
// البدء بالاتصال بقواعد البيانات المطلوبة
|
||||
$con_tracking = Database::get('tracking');
|
||||
$con_ride = Database::get('ride');
|
||||
// $con (main) تم تعريفه بالفعل في connect.php
|
||||
$mode = isset($_GET['mode']) && $_GET['mode'] == 'day' ? 'day' : 'live';
|
||||
|
||||
if ($mode == 'day') {
|
||||
$fileName = 'locations_day.json';
|
||||
$timeCondition = "DATE(created_at) = CURDATE()";
|
||||
} else {
|
||||
$fileName = 'locations_live.json';
|
||||
$freshSeconds = 1200;
|
||||
$timeCondition = "created_at >= NOW() - INTERVAL $freshSeconds SECOND";
|
||||
}
|
||||
|
||||
// تحديد المسار الكامل بدقة
|
||||
$savePath = __DIR__ . '/' . $fileName;
|
||||
|
||||
// === فحص صلاحيات الكتابة ===
|
||||
if (!is_writable(__DIR__)) {
|
||||
// إذا لم تكن هناك صلاحية، سنطبع الخطأ ونوقف التنفيذ
|
||||
echo json_encode([
|
||||
"status" => "error",
|
||||
"message" => "Permission Denied: Cannot write to directory. Please chmod 777 this folder.",
|
||||
"path" => __DIR__
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. جلب المواقع
|
||||
$sql_locations = "
|
||||
SELECT t.driver_id,
|
||||
t.latitude AS lat,
|
||||
t.longitude AS lon,
|
||||
t.heading,
|
||||
t.speed,
|
||||
t.created_at
|
||||
FROM car_tracks t
|
||||
INNER JOIN (
|
||||
SELECT driver_id, MAX(id) AS max_id
|
||||
FROM car_tracks
|
||||
WHERE $timeCondition
|
||||
GROUP BY driver_id
|
||||
) latest
|
||||
ON t.id = latest.max_id
|
||||
ORDER BY t.created_at DESC
|
||||
";
|
||||
$stmt = $con_tracking->prepare($sql_locations);
|
||||
$stmt->execute();
|
||||
$locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 2. جلب بيانات السائقين
|
||||
$driver_ids = array_unique(array_column($locations, 'driver_id'));
|
||||
$drivers_info = [];
|
||||
|
||||
if (!empty($driver_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
|
||||
$sql_drivers = "SELECT id, first_name, last_name, phone FROM driver WHERE id IN ($placeholders)";
|
||||
$stmt_drivers = $con->prepare($sql_drivers);
|
||||
$stmt_drivers->execute(array_values($driver_ids));
|
||||
foreach ($stmt_drivers->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$drivers_info[$row['id']] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. الدمج
|
||||
$final_drivers = [];
|
||||
foreach ($locations as $loc) {
|
||||
$d_id = $loc['driver_id'];
|
||||
$merged = [
|
||||
'id' => $d_id,
|
||||
'lat' => $loc['lat'],
|
||||
'lon' => $loc['lon'],
|
||||
'heading' => $loc['heading'],
|
||||
'speed' => $loc['speed'],
|
||||
'name' => 'Unknown',
|
||||
'phone' => '',
|
||||
'completed' => 0,
|
||||
'cancelled' => 0
|
||||
];
|
||||
|
||||
if (isset($drivers_info[$d_id])) {
|
||||
$info = $drivers_info[$d_id];
|
||||
// فك التشفير البسيط (تأكد من عمل encryptionHelper)
|
||||
if (isset($encryptionHelper)) {
|
||||
try { $info['first_name'] = $encryptionHelper->decryptData($info['first_name']); } catch(Exception $e){}
|
||||
try { $info['last_name'] = $encryptionHelper->decryptData($info['last_name']); } catch(Exception $e){}
|
||||
try { $info['phone'] = $encryptionHelper->decryptData($info['phone']); } catch(Exception $e){}
|
||||
}
|
||||
|
||||
$merged['name'] = trim(($info['first_name']??'') . ' ' . ($info['last_name']??''));
|
||||
$merged['phone'] = $info['phone'] ?? '';
|
||||
$merged['completed'] = $info['completed'] ?? 0;
|
||||
$merged['cancelled'] = $info['cancelled'] ?? 0;
|
||||
}
|
||||
$final_drivers[] = $merged;
|
||||
}
|
||||
|
||||
// 4. الحفظ
|
||||
$jsonContent = json_encode(['drivers' => $final_drivers, 'last_updated' => date('Y-m-d H:i:s')], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// محاولة الحفظ
|
||||
if (file_put_contents($savePath, $jsonContent) !== false) {
|
||||
echo json_encode(["status" => "success", "message" => "File written successfully to $savePath"]);
|
||||
} else {
|
||||
echo json_encode(["status" => "error", "message" => "Failed to write file. Check permissions."]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["status" => "error", "message" => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
24
backend/ride/location/get_location_area_links.php
Normal file
24
backend/ride/location/get_location_area_links.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
|
||||
// Use prepared statement to prevent SQL injection
|
||||
$sql = "
|
||||
SELECT * FROM `server_locations`
|
||||
";
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($car_locations) {
|
||||
jsonSuccess($car_locations);
|
||||
} else {
|
||||
jsonError("No car locations found");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
}
|
||||
86
backend/ride/location/getfemalbehavior.php
Normal file
86
backend/ride/location/getfemalbehavior.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
try {
|
||||
$southwestLat = filterRequest("southwestLat");
|
||||
$southwestLon = filterRequest("southwestLon");
|
||||
$northeastLat = filterRequest("northeastLat");
|
||||
$northeastLon = filterRequest("northeastLon");
|
||||
|
||||
$sql = "
|
||||
SELECT
|
||||
cl.driver_id,
|
||||
cl.latitude,
|
||||
cl.longitude,
|
||||
cl.heading,
|
||||
cl.speed,
|
||||
cl.status,
|
||||
cl.created_at,
|
||||
cl.updated_at,
|
||||
d.phone,
|
||||
d.email,
|
||||
d.birthdate,
|
||||
d.first_name,
|
||||
d.last_name,
|
||||
d.gender,
|
||||
d.maritalStatus,
|
||||
cr.make,
|
||||
cr.car_plate,
|
||||
cr.model,
|
||||
cr.color,
|
||||
cr.vin,
|
||||
cr.color_hex,
|
||||
cr.year,
|
||||
dt.token,
|
||||
COALESCE(AVG(rd.rating), 0) AS ratingDriver,
|
||||
COUNT(rd.id) AS ratingCount,
|
||||
TIMESTAMPDIFF(YEAR, d.birthdate, CURDATE()) AS age,
|
||||
(
|
||||
SELECT COALESCE(AVG(sub.behavior_score), 100)
|
||||
FROM (
|
||||
SELECT behavior_score
|
||||
FROM driver_behavior db
|
||||
WHERE db.driver_id = cl.driver_id
|
||||
ORDER BY db.id DESC
|
||||
LIMIT 5
|
||||
) AS sub
|
||||
) AS ai_behavior_score
|
||||
FROM
|
||||
car_locations cl
|
||||
LEFT JOIN driver d ON
|
||||
d.id = cl.driver_id
|
||||
LEFT JOIN CarRegistration cr ON
|
||||
cr.driverID = cl.driver_id
|
||||
LEFT JOIN driverToken dt ON
|
||||
dt.captain_id = cl.driver_id
|
||||
LEFT JOIN ratingDriver rd ON
|
||||
rd.driver_id = cl.driver_id
|
||||
WHERE
|
||||
cl.latitude >= :southwestLat AND cl.latitude <= :northeastLat
|
||||
AND cl.longitude >= :southwestLon AND cl.longitude <= :northeastLon
|
||||
AND cl.status = 'off'
|
||||
AND cl.updated_at >= NOW() - INTERVAL 5 SECOND
|
||||
AND (cr.make NOT LIKE '%دراجة%' OR cr.model NOT LIKE '%دراجة%')
|
||||
AND d.gender = 'Female'
|
||||
GROUP BY cl.driver_id
|
||||
ORDER BY ratingDriver DESC, cl.updated_at DESC
|
||||
LIMIT 10;
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql);
|
||||
$stmt->bindParam(':southwestLat', $southwestLat);
|
||||
$stmt->bindParam(':southwestLon', $southwestLon);
|
||||
$stmt->bindParam(':northeastLat', $northeastLat);
|
||||
$stmt->bindParam(':northeastLon', $northeastLon);
|
||||
|
||||
$stmt->execute();
|
||||
$car_locations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($car_locations) {
|
||||
jsonSuccess($car_locations);
|
||||
} else {
|
||||
jsonError("No car locations found");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
}
|
||||
?>
|
||||
254
backend/ride/location/print.php
Executable file
254
backend/ride/location/print.php
Executable file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// 1. DATA FETCHING (Dynamic from Server File)
|
||||
// ============================================================
|
||||
|
||||
$source_file = 'active_drivers_cache.json';
|
||||
|
||||
// Check if file exists to avoid errors
|
||||
if (!file_exists($source_file)) {
|
||||
die('<div style="font-family:sans-serif; text-align:center; padding:50px; color:#dc2626;">
|
||||
<h1>⚠️ Error: Data File Not Found</h1>
|
||||
<p>Please ensure <strong>'.$source_file.'</strong> exists in the same directory.</p>
|
||||
</div>');
|
||||
}
|
||||
|
||||
// Get content
|
||||
$json_data = file_get_contents($source_file);
|
||||
$data = json_decode($json_data, true);
|
||||
|
||||
if (!$data) {
|
||||
die('Error decoding JSON data.');
|
||||
}
|
||||
|
||||
$drivers = $data['data'];
|
||||
$last_updated = $data['last_updated'] ?? date('Y-m-d H:i:s');
|
||||
|
||||
// ============================================================
|
||||
// 2. DATA PROCESSING (Logic Layer)
|
||||
// ============================================================
|
||||
|
||||
$processed_drivers = [];
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'elite' => 0, // +50 hours
|
||||
'stable' => 0, // +20 hours
|
||||
'experimental' => 0, // +5 hours
|
||||
'inactive' => 0
|
||||
];
|
||||
|
||||
foreach ($drivers as $driver) {
|
||||
// Parse Active Time String (e.g., "293 ساعة و 48 دقيقة")
|
||||
$timeStr = $driver['active_time'] ?? "0 ساعة و 0 دقيقة";
|
||||
preg_match('/(\d+)\s*ساعة/', $timeStr, $hoursMatch);
|
||||
preg_match('/(\d+)\s*دقيقة/', $timeStr, $minsMatch);
|
||||
|
||||
$hours = isset($hoursMatch[1]) ? (int)$hoursMatch[1] : 0;
|
||||
$mins = isset($minsMatch[1]) ? (int)$minsMatch[1] : 0;
|
||||
$totalMinutes = ($hours * 60) + $mins;
|
||||
|
||||
// Categorize Driver
|
||||
$category = 'inactive';
|
||||
$catLabel = 'خامل';
|
||||
$catClass = 'cat-inactive';
|
||||
|
||||
if ($totalMinutes >= 3000) { // 50 hours
|
||||
$category = 'elite';
|
||||
$catLabel = 'نخبة';
|
||||
$catClass = 'cat-elite';
|
||||
$stats['elite']++;
|
||||
} elseif ($totalMinutes >= 1200) { // 20 hours
|
||||
$category = 'stable';
|
||||
$catLabel = 'مستقر';
|
||||
$catClass = 'cat-stable';
|
||||
$stats['stable']++;
|
||||
} elseif ($totalMinutes >= 300) { // 5 hours
|
||||
$category = 'experimental';
|
||||
$catLabel = 'تجريبي';
|
||||
$catClass = 'cat-experimental';
|
||||
$stats['experimental']++;
|
||||
} else {
|
||||
$stats['inactive']++;
|
||||
}
|
||||
|
||||
$driver['total_minutes'] = $totalMinutes;
|
||||
$driver['category_label'] = $catLabel;
|
||||
$driver['category_class'] = $catClass;
|
||||
$processed_drivers[] = $driver;
|
||||
}
|
||||
|
||||
$stats['total'] = count($processed_drivers);
|
||||
|
||||
// Sort by Active Time (High to Low)
|
||||
usort($processed_drivers, function($a, $b) {
|
||||
return $b['total_minutes'] <=> $a['total_minutes'];
|
||||
});
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>تقرير السائقين - Intaleq</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--color-elite: #d97706;
|
||||
--color-stable: #059669;
|
||||
--color-exp: #2563eb;
|
||||
--color-inactive: #dc2626;
|
||||
}
|
||||
body {
|
||||
font-family: 'Tajawal', sans-serif;
|
||||
background: #e5e7eb; /* Grey bg for screen */
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
color: #1f2937;
|
||||
}
|
||||
.page {
|
||||
background: white;
|
||||
max-width: 210mm; /* A4 Width */
|
||||
margin: 0 auto;
|
||||
padding: 20px 40px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
min-height: 297mm;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid #1f2937;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.header h1 { margin: 0; font-size: 24px; }
|
||||
.meta { font-size: 12px; color: #6b7280; }
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.stat-box {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-value { font-size: 24px; font-weight: 800; display: block; }
|
||||
.stat-label { font-size: 12px; color: #6b7280; font-weight: bold; }
|
||||
|
||||
/* Table */
|
||||
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||||
th, td { border-bottom: 1px solid #e5e7eb; padding: 10px; text-align: right; }
|
||||
th { background-color: #f9fafb; font-weight: 800; color: #374151; border-top: 2px solid #374151; }
|
||||
tr:nth-child(even) { background-color: #fdfdfd; }
|
||||
|
||||
/* Categories */
|
||||
.badge { padding: 2px 8px; border-radius: 4px; font-weight: bold; font-size: 10px; border: 1px solid; }
|
||||
.cat-elite { background: #fef3c7; color: var(--color-elite); border-color: #fcd34d; }
|
||||
.cat-stable { background: #d1fae5; color: var(--color-stable); border-color: #6ee7b7; }
|
||||
.cat-experimental { background: #dbeafe; color: var(--color-exp); border-color: #93c5fd; }
|
||||
.cat-inactive { background: #fee2e2; color: var(--color-inactive); border-color: #fca5a5; }
|
||||
|
||||
/* Notes Column */
|
||||
.notes-col { width: 150px; border-left: 1px dashed #d1d5db; }
|
||||
|
||||
/* Print Rules */
|
||||
@media print {
|
||||
body { background: white; padding: 0; }
|
||||
.page { box-shadow: none; max-width: 100%; padding: 0; margin: 0; }
|
||||
.no-print { display: none; }
|
||||
th { background-color: #f3f4f6 !important; -webkit-print-color-adjust: exact; }
|
||||
.badge { -webkit-print-color-adjust: exact; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="no-print" style="text-align: center; margin-bottom: 20px;">
|
||||
<button onclick="window.print()" style="padding: 10px 20px; background: #2563eb; color: white; border: none; border-radius: 5px; cursor: pointer; font-family: inherit; font-weight: bold; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
🖨️ طباعة التقرير (PDF)
|
||||
</button>
|
||||
<button onclick="location.reload()" style="padding: 10px 20px; background: #4b5563; color: white; border: none; border-radius: 5px; cursor: pointer; font-family: inherit; margin-right: 10px;">
|
||||
🔄 تحديث البيانات
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>تقرير أداء السائقين</h1>
|
||||
<div class="meta">تاريخ الطباعة: <?php echo date('Y-m-d H:i'); ?></div>
|
||||
</div>
|
||||
<div style="text-align: left">
|
||||
<h2 style="margin:0; color: #2563eb;">Intaleq</h2>
|
||||
<div class="meta dir-ltr"><?php echo $last_updated; ?> :آخر تحديث بيانات</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-box" style="border-bottom: 3px solid #374151;">
|
||||
<span class="stat-label">إجمالي السائقين</span>
|
||||
<span class="stat-value" style="color: #374151"><?php echo $stats['total']; ?></span>
|
||||
</div>
|
||||
<div class="stat-box" style="border-bottom: 3px solid var(--color-elite);">
|
||||
<span class="stat-label">النخبة (+50 ساعة)</span>
|
||||
<span class="stat-value" style="color: var(--color-elite)"><?php echo $stats['elite']; ?></span>
|
||||
</div>
|
||||
<div class="stat-box" style="border-bottom: 3px solid var(--color-stable);">
|
||||
<span class="stat-label">مستقرون (+20 ساعة)</span>
|
||||
<span class="stat-value" style="color: var(--color-stable)"><?php echo $stats['stable']; ?></span>
|
||||
</div>
|
||||
<div class="stat-box" style="border-bottom: 3px solid var(--color-inactive);">
|
||||
<span class="stat-label">يحتاجون متابعة</span>
|
||||
<span class="stat-value" style="color: var(--color-inactive)"><?php echo $stats['inactive']; ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 5%">#</th>
|
||||
<th style="width: 25%">اسم السائق</th>
|
||||
<th style="width: 15%">رقم الهاتف</th>
|
||||
<th style="width: 20%">ساعات النشاط</th>
|
||||
<th style="width: 10%">الحالة</th>
|
||||
<th style="width: 10%">تاريخ الانضمام</th>
|
||||
<th class="notes-col">ملاحظات إدارية</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $i = 1; foreach ($processed_drivers as $driver): ?>
|
||||
<tr>
|
||||
<td><?php echo $i++; ?></td>
|
||||
<td>
|
||||
<strong><?php echo $driver['name_arabic'] ?? '---'; ?></strong>
|
||||
</td>
|
||||
<td style="direction: ltr; text-align: right; font-family: monospace;">
|
||||
<?php echo $driver['phone']; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $driver['active_time']; ?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?php echo $driver['category_class']; ?>">
|
||||
<?php echo $driver['category_label']; ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?php echo isset($driver['created_at']) ? date('Y-m-d', strtotime($driver['created_at'])) : '-'; ?></td>
|
||||
<td class="notes-col"></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
58
backend/ride/location/save_behavior.php
Normal file
58
backend/ride/location/save_behavior.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// تأكد من أن ملف connect.php يقوم بتهيئة اتصالين:
|
||||
// $con -> يتصل بقاعدة البيانات الأساسية
|
||||
// $con_tracking -> يتصل بقاعدة بيانات التتبع (driver_behavior, car_locations)
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
try {
|
||||
// استلام البيانات من Flutter
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$trip_id = filterRequest("trip_id");
|
||||
$max_speed = filterRequest("max_speed");
|
||||
$avg_speed = filterRequest("avg_speed");
|
||||
$hard_brakes = filterRequest("hard_brakes");
|
||||
$total_distance = filterRequest("total_distance");
|
||||
$behavior_score = filterRequest("behavior_score");
|
||||
|
||||
// تحقق من القيم الأساسية
|
||||
if (empty($driver_id) || empty($trip_id)) {
|
||||
jsonError("Missing driver_id or trip_id");
|
||||
exit();
|
||||
}
|
||||
|
||||
// إدخال البيانات في جدول driver_behavior باستخدام اتصال التتبع
|
||||
// تم تغيير $con إلى $con_tracking
|
||||
$stmt = $con_tracking->prepare("
|
||||
INSERT INTO driver_behavior (
|
||||
driver_id, trip_id, max_speed, avg_speed,
|
||||
hard_brakes, total_distance, behavior_score
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$driver_id,
|
||||
$trip_id,
|
||||
$max_speed,
|
||||
$avg_speed,
|
||||
$hard_brakes,
|
||||
$total_distance,
|
||||
$behavior_score
|
||||
]);
|
||||
|
||||
// التحقق من نجاح العملية
|
||||
if ($stmt->rowCount() > 0) {
|
||||
jsonSuccess(null, "Behavior data saved");
|
||||
} else {
|
||||
// في حالة عدم حدوث خطأ، ولكن لم يتم إدخال صف (قد يحدث)،
|
||||
// من الأفضل إرجاع رسالة فشل عامة.
|
||||
jsonError("Failed to save data (No rows affected)");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
jsonError("Database error: " . $e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
jsonError("Internal error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// تم حذف exit() من هنا ليتم التعامل معها داخل try/catch
|
||||
?>
|
||||
69
backend/ride/location/update.php
Normal file
69
backend/ride/location/update.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../../connect.php';
|
||||
|
||||
$driver_id = filterRequest("driver_id");
|
||||
$latitude = filterRequest("latitude");
|
||||
$longitude = filterRequest("longitude");
|
||||
$status = filterRequest("status");
|
||||
$heading = filterRequest("heading");
|
||||
$speed = filterRequest("speed");
|
||||
$distance = filterRequest("distance");
|
||||
$updated_at = date("Y-m-d H:i:s");
|
||||
|
||||
// Basic validation
|
||||
if (!$driver_id || !$latitude || !$longitude || $status === null) {
|
||||
http_response_code(400);
|
||||
// Use your custom printFailure function for consistency
|
||||
jsonError('Missing required fields');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Secure SQL using prepared statement
|
||||
$sql = "INSERT INTO `car_locations` (
|
||||
`driver_id`, `latitude`, `longitude`, `heading`, `speed`, `distance`, `status`, `updated_at`
|
||||
) VALUES (
|
||||
:driver_id, :latitude, :longitude, :heading, :speed, :distance, :status, :updated_at
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`latitude` = VALUES(`latitude`),
|
||||
`longitude` = VALUES(`longitude`),
|
||||
`heading` = VALUES(`heading`),
|
||||
`speed` = VALUES(`speed`),
|
||||
`distance` = VALUES(`distance`),
|
||||
`status` = VALUES(`status`),
|
||||
`updated_at` = VALUES(`updated_at`)";
|
||||
|
||||
try {
|
||||
$stmt = $con->prepare($sql);
|
||||
|
||||
// The execute method returns true on success and false on failure.
|
||||
$success = $stmt->execute([
|
||||
':latitude' => $latitude,
|
||||
':longitude' => $longitude,
|
||||
':heading' => $heading,
|
||||
':speed' => $speed,
|
||||
':distance' => $distance,
|
||||
':status' => $status,
|
||||
':updated_at' => $updated_at,
|
||||
':driver_id' => $driver_id
|
||||
]);
|
||||
|
||||
// The reliable way to check for success is if execute() returns true
|
||||
// and doesn't throw an exception. We no longer need rowCount().
|
||||
if ($success) {
|
||||
// Print a success message
|
||||
jsonSuccess(null, "Car location updated successfully");
|
||||
} else {
|
||||
// This case is rare but might happen if execute fails without an exception
|
||||
jsonError("Failed to update car location");
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// A real database error occurred.
|
||||
http_response_code(500);
|
||||
// You can log the detailed error for debugging
|
||||
// error_log('Database error: ' . $e->getMessage());
|
||||
jsonError('Database error occurred');
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user