let map;
let userMarker;
function initMap() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const userLocation = { lat: position.coords.latitude, lng: position.coords.longitude };
map = new google.maps.Map(document.getElementById("map"), {
center: userLocation,
zoom: 14,
disableDefaultUI: true
});
userMarker = new google.maps.Marker({
position: userLocation,
map: map,
icon: {
path: google.maps.SymbolPath.CIRCLE,
fillColor: '#4285F4',
fillOpacity: 1,
strokeColor: 'white',
strokeWeight: 2,
scale: 7
}
});
addSmartGarages(userLocation);
},
() => { alert("Please allow location access to display your position on the map."); }
);
}
}
// 🎯 الدالة الذكية والمطابقة بالمللي لـ navigation.js من حيث الحسابات والـ API والترتيب
async function addSmartGarages(userLocation) {
console.log("📡 Live Data from Server:", liveGaragesData); // للـ Debugging
if (typeof liveGaragesData === 'undefined' || !liveGaragesData) return;
const bounds = new google.maps.LatLngBounds();
const cards = Array.from(document.querySelectorAll('.garage-card'));
const garagesContainer = document.querySelector('.nearest-garages');
// 1. تصفية واستخراج الجراجات اللي ليها إحداثيات سليمة
let garagesList = liveGaragesData.map(garage => {
const lat = parseFloat(garage.lat);
const lng = parseFloat(garage.lng);
return { ...garage, lat, lng };
}).filter(g => !isNaN(g.lat) && !isNaN(g.lng) && (g.lat !== 0 || g.lng !== 0));
// 2. نفس معادلة الزحمة (Traffic Simulation) اللي في navigation.js بالظبط
const currentHour = new Date().getHours();
let trafficMultiplier = 1.3; // زحمة عادية في مصر
if ((currentHour >= 8 && currentHour <= 10) || (currentHour >= 14 && currentHour <= 19)) {
trafficMultiplier = 1.8; // وقت الذروة
}
// 3. سحب البيانات الحقيقية من الشوارع لكل الجراجات بالتوازي (صورة طبق الأصل من الـ Navigation)
const fetchPromises = garagesList.map(async (garage) => {
let routedDistance = Infinity;
let estimatedMinutes = Infinity;
let timeFormatted = "-- mins";
let distanceFormatted = "-- km";
try {
// نفس الـ URL المستخدم جوه الـ Navigation بالظبط
const url = `https://router.project-osrm.org/route/v1/driving/${userLocation.lng},${userLocation.lat};${garage.lng},${garage.lat}?overview=false`;
const response = await fetch(url);
const data = await response.json();
if (data.routes && data.routes.length > 0) {
routedDistance = data.routes[0].distance; // بالمتر (على الشارع)
const baseDuration = data.routes[0].duration; // بالثانية (طريق فاضي)
const realisticSeconds = baseDuration * trafficMultiplier;
estimatedMinutes = Math.ceil(realisticSeconds / 60);
// ==========================================
// 💡 تنسيق الوقت المتطابق بالمللي (ساعات ودقائق)
// ==========================================
if (estimatedMinutes <= 1) {
timeFormatted = "Less than a min";
} else if (estimatedMinutes < 60) {
timeFormatted = `${estimatedMinutes} mins`;
} else {
const hours = Math.floor(estimatedMinutes / 60);
const mins = estimatedMinutes % 60;
timeFormatted = mins > 0 ? `${hours} h ${mins} mins` : `${hours} h`;
}
// ==========================================
// 💡 تنسيق المسافة المتطابق بالمللي (متر وكيلومتر)
// ==========================================
if (routedDistance < 1000) {
distanceFormatted = `${Math.round(routedDistance)} m`;
} else {
const km = (routedDistance / 1000).toFixed(1);
distanceFormatted = `${km} km`;
}
}
} catch (err) {
console.error("⚠️ ETA Fetch error for " + garage.name + ":", err);
// نظام احتياطي (Fallback) في حالة سقوط السيرفر باستخدام الهيفرسين
routedDistance = calculateDistance(userLocation.lat, userLocation.lng, garage.lat, garage.lng);
estimatedMinutes = Math.ceil((routedDistance / 1000) * 2 * trafficMultiplier);
timeFormatted = `${estimatedMinutes} mins`;
distanceFormatted = `${(routedDistance / 1000).toFixed(1)} km`;
}
return {
...garage,
routedDistance,
estimatedMinutes,
timeFormatted,
distanceFormatted
};
});
// الانتظار حتى اكتمال جلب البيانات لكل الجراجات
let garagesWithRealTime = await Promise.all(fetchPromises);
// ==============================================================
// 💡 4. الترتيب الثنائي الصارم (الأقل وقتاً أولاً، ثم الأقل مسافة)
// ==============================================================
garagesWithRealTime.sort((a, b) => {
if (a.estimatedMinutes === b.estimatedMinutes) {
return a.routedDistance - b.routedDistance; // لو الوقت متساوي، يرتب بالأقرب مسافة
}
return a.estimatedMinutes - b.estimatedMinutes; // الترتيب الأساسي بالوقت الأقل
});
// 5. قطف أقرب 3 جراجات فقط بناءً على الترتيب الجديد
const top3Garages = garagesWithRealTime.slice(0, 4);
// 6. تحديث الكروت وتطبيق الفلترة والاختفاء في الـ HTML
cards.forEach(card => {
const titleElement = card.querySelector('h4');
if (titleElement) {
const htmlName = titleElement.innerText.trim().toLowerCase();
// البحث عن الجراج المطابق في القائمة الحية لأقرب 3 جراجات
const matchingGarage = top3Garages.find(g =>
htmlName.includes(g.name.trim().toLowerCase()) ||
g.name.trim().toLowerCase().includes(htmlName)
);
if (matchingGarage) {
const distanceElement = card.querySelector('p');
if (distanceElement) {
// إظهار الوقت والمسافة بتنسيق فخم ومتطابق مع البار السفلي للملاحة
distanceElement.innerHTML = `
${matchingGarage.timeFormatted}
|
${matchingGarage.distanceFormatted}`;
}
card.style.display = 'block'; // إظهار الكارت
card.dataset.sortOrder = top3Garages.indexOf(matchingGarage); // حفظ الترتيب الصحيح
} else {
card.style.display = 'none'; // إخفاء الجراج لو مش من التوب 3 القريبين
}
}
});
// 7. إعادة ترتيب الكروت داخل الـ Container في الصفحة لتعكس الترتيب الثنائي بدقة
const visibleCards = Array.from(document.querySelectorAll('.garage-card')).filter(c => c.style.display !== 'none');
visibleCards.sort((a, b) => parseInt(a.dataset.sortOrder) - parseInt(b.dataset.sortOrder));
visibleCards.forEach(card => garagesContainer.appendChild(card));
// 8. إسقاط الدبابيس (Markers) لأقرب 3 جراجات فقط على الخريطة
top3Garages.forEach(garage => {
const garageLocation = { lat: garage.lat, lng: garage.lng };
bounds.extend(garageLocation);
const marker = new google.maps.Marker({
position: garageLocation,
map: map,
title: garage.name
});
const statusClass = garage.free_spots > 0 ? "free-slots" : "busy-slots";
const statusText = garage.free_spots > 0 ? `${garage.free_spots} spots available` : "Fully occupied";
const infoContent = `
${garage.name}
Capacity: ${garage.total_spots} cars
${statusText}
`;
const infowindow = new google.maps.InfoWindow({ content: infoContent });
marker.addListener("click", () => { infowindow.open(map, marker); });
});
// 9. تركيز كاميرا الخريطة لتشمل المستخدم وأقرب جراجات بحجم مثالي
if (!bounds.isEmpty()) {
bounds.extend(userLocation);
map.fitBounds(bounds);
}
}
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371000;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
return R * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)));
}
/* =========================================
Booking Modal Logic & Dynamic Pricing
========================================= */
let currentPrices = { standard: 0, vip: 0, special_needs: 0 };
async function fetchDynamicPrices(garageId) {
try {
document.getElementById('price-standard').innerText = '...';
document.getElementById('price-vip').innerText = '...';
document.getElementById('price-special').innerText = '...';
if(document.getElementById('finalPriceDisplay')) document.getElementById('finalPriceDisplay').innerText = '...';
const response = await fetch(`/api/get_garage_prices?garage_id=${garageId}`);
const data = await response.json();
if (data.status === 'success') {
currentPrices.standard = data.prices.standard;
currentPrices.vip = data.prices.vip;
currentPrices.special_needs = data.prices.special_needs;
document.getElementById('price-standard').innerText = `${currentPrices.standard} EGP/h`;
document.getElementById('price-vip').innerText = `${currentPrices.vip} EGP/h`;
document.getElementById('price-special').innerText = `${currentPrices.special_needs} EGP/h`;
calculatePrice();
} else {
console.error("Backend Error:", data.message);
alert("Error loading prices: " + data.message);
}
} catch (error) {
console.error("Network error:", error);
}
}
const bookingModal = document.getElementById('bookingModal');
const closeBookingBtn = document.getElementById('closeBookingBtn');
const openBookingBtns = document.querySelectorAll('.open-booking-btn');
const minusBtn = document.getElementById('minusHour');
const plusBtn = document.getElementById('plusHour');
const durationDisplay = document.getElementById('durationDisplay');
const spotRadios = document.querySelectorAll('input[name="spotType"]');
const finalPriceDisplay = document.getElementById('finalPriceDisplay');
const bookingForm = document.getElementById('bookingForm');
const vehicleSelect = document.getElementById('vehicleSelect');
let currentHours = 1;
let currentGarageId = 1;
async function fetchUserVehicles() {
if (!vehicleSelect) return;
vehicleSelect.innerHTML = '';
try {
const response = await fetch('/api/get_user_cars');
const data = await response.json();
if (data.status === 'success') {
vehicleSelect.innerHTML = '';
if (data.cars.length === 0) {
vehicleSelect.innerHTML = '';
return;
}
data.cars.forEach((car, index) => {
const option = document.createElement('option');
option.value = car.id;
option.text = `🚗 Plate: ${car.plate_no}`;
if (index === 0) option.selected = true;
vehicleSelect.appendChild(option);
});
} else {
vehicleSelect.innerHTML = '';
}
} catch (error) {
vehicleSelect.innerHTML = '';
}
}
function initializeModalData() {
document.getElementById('price-standard').innerText = '...';
document.getElementById('price-vip').innerText = '...';
document.getElementById('price-special').innerText = '...';
if(finalPriceDisplay) finalPriceDisplay.innerText = '...';
currentHours = 1;
if(durationDisplay) durationDisplay.innerText = currentHours;
// =========================================================
// 💡 التعديل هنا: تصفير السلوت وإرجاع زرار الاختيار لشكله الطبيعي
// =========================================================
userChosenSlot = null; // تفريغ المتغير اللي بيتبعت للداتا بيز
if(chooseSlotBtn) chooseSlotBtn.style.display = 'block'; // إظهار زرار "Choose Your Slot"
if(selectedSlotDisplay) selectedSlotDisplay.style.display = 'none'; // إخفاء المربع اللي فيه السلوت القديم
}
if (openBookingBtns.length > 0) {
openBookingBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
if (btn.hasAttribute('disabled')) return;
const card = btn.closest('.garage-card');
const garageName = card.querySelector('h4').innerText;
localStorage.setItem('selectedGarageName', garageName);
const modalGarageNameEl = document.getElementById('modalGarageName');
if (modalGarageNameEl) {
modalGarageNameEl.innerText = garageName;
}
initializeModalData();
let garageId = 1;
if(garageName.includes("Festival")) garageId = 1;
else if(garageName.includes("Citystars")) garageId = 2;
else if(garageName.includes("Arabia")) garageId = 3;
else if(garageName.includes("Egypt")) garageId = 4;
else if(garageName.includes("Almaza")) garageId = 5;
else if(garageName.includes("Downtown")) garageId = 6;
currentGarageId = garageId;
fetchUserVehicles();
fetchDynamicPrices(garageId);
if(bookingModal) bookingModal.classList.add('active');
});
});
}
if (closeBookingBtn) {
closeBookingBtn.addEventListener('click', () => {
if(bookingModal) bookingModal.classList.remove('active');
});
}
function calculatePrice() {
const checkedRadio = document.querySelector('input[name="spotType"]:checked');
if(!checkedRadio || !finalPriceDisplay) return;
const selectedSpot = checkedRadio.value;
const pricePerHour = currentPrices[selectedSpot];
if (pricePerHour > 0) {
let total = Math.round(pricePerHour * currentHours);
finalPriceDisplay.innerText = total;
}
}
if (plusBtn) {
plusBtn.addEventListener('click', () => {
if (currentHours < 24) {
currentHours++;
durationDisplay.innerText = currentHours;
calculatePrice();
}
});
}
if (minusBtn) {
minusBtn.addEventListener('click', () => {
if (currentHours > 1) {
currentHours--;
durationDisplay.innerText = currentHours;
calculatePrice();
}
});
}
if (spotRadios.length > 0) {
spotRadios.forEach(radio => {
radio.addEventListener('change', calculatePrice);
});
}
const parkingSlotsData = {
vip: [
{ id: 'V-01', status: 'available' },
{ id: 'V-02', status: 'occupied' },
{ id: 'V-03', status: 'available' }
],
special_needs: [
{ id: 'S-01', status: 'available' },
{ id: 'S-02', status: 'occupied' }
],
standard: [
{ id: 'A-01', status: 'occupied' },
{ id: 'A-02', status: 'available' },
{ id: 'A-03', status: 'available' },
{ id: 'A-04', status: 'occupied' },
{ id: 'A-05', status: 'available' },
{ id: 'A-06', status: 'occupied' }
]
};
const chooseSlotBtn = document.getElementById('chooseSlotBtn');
const slotDashboardModal = document.getElementById('slotDashboardModal');
const closeSlotDashboardBtn = document.getElementById('closeSlotDashboardBtn');
const selectedSlotDisplay = document.getElementById('selectedSlotDisplay');
const chosenSlotText = document.getElementById('chosenSlotText');
const changeSlotBtn = document.getElementById('changeSlotBtn');
let userChosenSlot = null;
function renderParkingLayout() {
const checkedRadio = document.querySelector('input[name="spotType"]:checked');
if(!checkedRadio) return;
const currentCategory = checkedRadio.value;
const renderZone = (gridId, categoryKey) => {
const grid = document.getElementById(gridId);
if(!grid) return;
grid.innerHTML = '';
parkingSlotsData[categoryKey].forEach(slot => {
const isLocked = currentCategory !== categoryKey;
const isAvailable = slot.status === 'available';
let boxClass = 'slot-box-ui';
let icon = 'fa-car';
let statusText = '';
if (isLocked) {
boxClass += ' locked';
icon = 'fa-lock';
statusText = 'Locked';
} else if (isAvailable) {
boxClass += ' available';
statusText = 'متاح';
} else {
boxClass += ' occupied';
statusText = 'مشغول';
}
const clickAction = (!isLocked && isAvailable) ? `onclick="confirmSlotSelection('${slot.id}')"` : '';
const boxHTML = `
${slot.id}
${statusText}
`;
grid.innerHTML += boxHTML;
});
};
renderZone('vipSlotsGrid', 'vip');
renderZone('accessibleSlotsGrid', 'special_needs');
renderZone('standardSlotsGrid', 'standard');
}
// Update Choose Slot Button Click Listener
if(chooseSlotBtn) {
chooseSlotBtn.addEventListener('click', () => {
chooseSlotBtn.style.setProperty('border-color', '#00c2b8', 'important');
chooseSlotBtn.style.setProperty('color', '#00c2b8', 'important');
chooseSlotBtn.innerHTML = ' Choose Your Slot';
// 🚀 Call the live sync function instead of calling renderParkingLayout directly
fetchAndRenderLiveSlots(currentGarageId);
if(slotDashboardModal) slotDashboardModal.classList.add('active');
});
}
// Update Change Slot Button Click Listener
if(changeSlotBtn) {
changeSlotBtn.addEventListener('click', () => {
// 🚀 Call the live sync function here as well to renew availability states
fetchAndRenderLiveSlots(currentGarageId);
if(slotDashboardModal) slotDashboardModal.classList.add('active');
});
}
if(closeSlotDashboardBtn) {
closeSlotDashboardBtn.addEventListener('click', () => {
if(slotDashboardModal) slotDashboardModal.classList.remove('active');
});
}
window.confirmSlotSelection = function(slotId) {
userChosenSlot = slotId;
if(slotDashboardModal) slotDashboardModal.classList.remove('active');
if(chooseSlotBtn) chooseSlotBtn.style.display = 'none';
if(selectedSlotDisplay) selectedSlotDisplay.style.display = 'flex';
if(chosenSlotText) chosenSlotText.innerText = `Slot: ${slotId}`;
};
if (spotRadios.length > 0) {
spotRadios.forEach(radio => {
radio.addEventListener('change', () => {
calculatePrice();
userChosenSlot = null;
if(chooseSlotBtn) chooseSlotBtn.style.display = 'block';
if(selectedSlotDisplay) selectedSlotDisplay.style.display = 'none';
});
});
}
if (bookingForm) {
bookingForm.addEventListener('submit', async (e) => {
e.preventDefault();
if (!userChosenSlot) {
chooseSlotBtn.style.setProperty('border-color', '#ef4444', 'important');
chooseSlotBtn.style.setProperty('color', '#ef4444', 'important');
chooseSlotBtn.innerHTML = ' Please choose your slot first!';
return;
}
// =====================================================================
// 💡 التعديل الجديد: التشيك الذكي على رصيد المحفظة قبل الحجز
// =====================================================================
// 1. هنسحب التكلفة الإجمالية اللي ظهرت لليوزر
const finalPriceText = finalPriceDisplay.innerText;
const requiredAmount = parseFloat(finalPriceText);
// 2. هنسحب رصيد المحفظة من الهيدر فوق (وبنفلتره عشان لو مكتوب جنبه EGP)
const balanceElement = document.querySelector('.wallet-badge strong');
let currentBalance = 0;
if (balanceElement) {
// السطر ده بياخد الأرقام بس من النص (مثلاً "50.00 EGP" هيخليها 50.00)
currentBalance = parseFloat(balanceElement.innerText.replace(/[^\d.-]/g, ''));
}
// 3. المقارنة الحاسمة
if (currentBalance < requiredAmount) {
alert(`عفواً، رصيد محفظتك الحالي (${currentBalance} EGP) لا يكفي.\nتحتاج إلى شحن المحفظة بـ ${requiredAmount} EGP على الأقل لإتمام الحجز.`);
// توجيه تلقائي لصفحة المحفظة عشان يشحن
window.location.href = 'wallet.html';
return; // 🛑 بنوقف تنفيذ الكود هنا عشان ميبعتش حاجة للداتا بيز
}
// =====================================================================
const carId = vehicleSelect.value;
const spotType = document.querySelector('input[name="spotType"]:checked').value;
const reservedHourPrice = currentPrices[spotType];
if (reservedHourPrice === 0) {
alert("Please wait for the dynamic prices to load securely.");
return;
}
const submitBtn = document.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerHTML;
submitBtn.innerHTML = ' Saving...';
submitBtn.disabled = true;
try {
const response = await fetch('/api/create_reservation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
car_id: carId,
spot_type: spotType,
hour_price: reservedHourPrice,
duration: currentHours,
garage_id: currentGarageId,
slot_name: userChosenSlot
})
});
const result = await response.json();
if (result.status === 'success') {
const selectedVehicleText = vehicleSelect.options[vehicleSelect.selectedIndex].text.replace('🚗 Plate: ', '');
// حفظ بيانات الفاتورة
localStorage.setItem('bookingPrice', finalPriceText);
localStorage.setItem('bookedVehicle', selectedVehicleText);
localStorage.setItem('bookedSlot', userChosenSlot);
// 💡 التعديل هنا: استخراج إحداثيات الجراج اللي اتحجز وحفظها للـ Navigation
const targetGarage = liveGaragesData.find(g => g.garage_id === currentGarageId || g.name.includes(localStorage.getItem('selectedGarageName')));
if (targetGarage) {
localStorage.setItem('navGarageName', targetGarage.name);
localStorage.setItem('navGarageLat', targetGarage.lat);
localStorage.setItem('navGarageLng', targetGarage.lng);
}
// توجيه لصفحة التذكرة
window.location.href = 'payment.html';
} else {
alert("Database Error: " + result.message);
submitBtn.innerHTML = originalBtnText;
submitBtn.disabled = false;
}
} catch (error) {
console.error("Network error:", error);
alert("Network error while communicating with the server.");
submitBtn.innerHTML = originalBtnText;
submitBtn.disabled = false;
}
});
}
if(chooseSlotBtn) {
chooseSlotBtn.addEventListener('click', () => {
chooseSlotBtn.style.setProperty('border-color', '#00c2b8', 'important');
chooseSlotBtn.style.setProperty('color', '#00c2b8', 'important');
chooseSlotBtn.innerHTML = ' Choose Your Slot';
renderParkingLayout();
if(slotDashboardModal) slotDashboardModal.classList.add('active');
});
}
/* ==========================================================================
UI Components (Notifications, Avatar, Settings, Theme, Logout)
معزولة تماماً في الأسفل لعدم التأثير على الكود الأساسي
========================================================================== */
document.addEventListener("DOMContentLoaded", () => {
// 1. Theme Toggle
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
if (localStorage.getItem('theme') === 'light') {
document.body.classList.add('light-mode');
themeToggle.innerHTML = '';
}
themeToggle.addEventListener('click', () => {
document.body.classList.toggle('light-mode');
if (document.body.classList.contains('light-mode')) {
localStorage.setItem('theme', 'light');
themeToggle.innerHTML = '';
} else {
localStorage.setItem('theme', 'dark');
themeToggle.innerHTML = '';
}
});
}
// 2. Profile Dropdown Menu
const avatarMenuBtn = document.getElementById("avatarMenuBtn");
const avatarDropdown = document.getElementById("avatarDropdown");
if (avatarMenuBtn && avatarDropdown) {
avatarMenuBtn.addEventListener("click", (e) => {
e.stopPropagation();
avatarDropdown.classList.toggle("active");
});
window.addEventListener("click", (e) => {
if (avatarDropdown.classList.contains("active")) {
if (!avatarDropdown.contains(e.target) && !avatarMenuBtn.contains(e.target)) {
avatarDropdown.classList.remove("active");
}
}
});
}
// 3. Notification Sidebar
const notifTrigger = document.getElementById("notifTrigger");
const notifSidebar = document.getElementById("notifSidebar");
const notifOverlay = document.getElementById("notifOverlay");
const closeNotifBtn = document.getElementById("closeNotifBtn");
const notifBadge = document.getElementById("notifBadge");
if (notifTrigger && notifSidebar && notifOverlay) {
notifTrigger.addEventListener("click", (e) => {
e.stopPropagation();
notifSidebar.classList.add("active");
notifOverlay.classList.add("active");
if (notifBadge) notifBadge.style.display = "none";
});
}
const closeNotificationSidebar = () => {
if(notifSidebar) notifSidebar.classList.remove("active");
if(notifOverlay) notifOverlay.classList.remove("active");
};
if (closeNotifBtn) closeNotifBtn.addEventListener("click", closeNotificationSidebar);
if (notifOverlay) notifOverlay.addEventListener("click", closeNotificationSidebar);
// 4. Avatar Upload Modal
const changePicBtn = document.getElementById("changePicBtn");
const avatarUploadModal = document.getElementById("avatarUploadModal");
const closeAvatarModalBtn = document.getElementById("closeAvatarModalBtn");
const avatarFileInput = document.getElementById("avatarFileInput");
const avatarPreview = document.getElementById("avatarPreview");
const avatarUploadForm = document.getElementById("avatarUploadForm");
let selectedImageBase64 = null;
if (changePicBtn && avatarUploadModal) {
changePicBtn.addEventListener("click", (e) => {
e.preventDefault();
if (avatarDropdown) avatarDropdown.classList.remove("active");
avatarUploadModal.classList.add("active");
});
}
if (closeAvatarModalBtn) {
closeAvatarModalBtn.addEventListener("click", () => { avatarUploadModal.classList.remove("active"); });
}
if (avatarFileInput && avatarPreview) {
avatarFileInput.addEventListener("change", (e) => {
const file = e.target.files[0];
if (file) {
if (!file.type.startsWith('image/')) { alert("الرجاء اختيار ملف صورة فقط!"); return; }
const reader = new FileReader();
reader.onload = (event) => {
avatarPreview.src = event.target.result;
selectedImageBase64 = event.target.result;
};
reader.readAsDataURL(file);
}
});
}
if (avatarUploadForm) {
avatarUploadForm.addEventListener("submit", (e) => {
e.preventDefault();
if (selectedImageBase64) {
document.querySelectorAll(".user-avatar img, .dropdown-menu img, #mainPageAvatar").forEach(img => { img.src = selectedImageBase64; });
localStorage.setItem('user_custom_avatar', selectedImageBase64);
avatarUploadModal.classList.remove("active");
alert("تم تحديث صورتك الشخصية بنجاح! 👤✨");
} else {
alert("الرجاء اختيار صورة أولاً قبل الحفظ.");
}
});
}
// تطبيق الصورة المحفوظة تلقائياً في كل الصفحات
const savedAvatar = localStorage.getItem('user_custom_avatar');
if (savedAvatar) {
document.querySelectorAll(".user-avatar img, #mainPageAvatar").forEach(img => img.src = savedAvatar);
if (avatarPreview) avatarPreview.src = savedAvatar;
}
// 5. Account Settings Routing & Modal Handler
const accountSettingsBtn = document.getElementById("accountSettingsBtn");
if (accountSettingsBtn) {
accountSettingsBtn.setAttribute("href", "settings.html");
accountSettingsBtn.addEventListener("click", () => {
window.location.href = "settings.html";
});
}
const accountSettingsModal = document.getElementById('accountSettingsModal');
const closeSettingsModalBtn = document.getElementById('closeSettingsModalBtn');
if (closeSettingsModalBtn) {
closeSettingsModalBtn.addEventListener('click', () => {
if(accountSettingsModal) accountSettingsModal.classList.remove('active');
});
}
const pageSettingsForm = document.getElementById("pageSettingsForm");
if (pageSettingsForm) {
pageSettingsForm.addEventListener("submit", async (e) => {
e.preventDefault();
// هنا يوضع الـ API بتاع تغيير بيانات الحساب لو محتاجه
});
}
// 6. مسح ذاكرة التخزين المؤقت عند تسجيل الخروج (Logout Clearing)
const logoutElements = document.querySelectorAll('.logout-btn, .logout-link');
logoutElements.forEach(element => {
element.addEventListener('click', () => {
localStorage.removeItem('bookingPrice');
localStorage.removeItem('bookedVehicle');
localStorage.removeItem('bookedSlot');
localStorage.removeItem('selectedGarageName');
});
});
});
// 💡 Fetch live slot statuses from DB and update the local layout data dynamically
async function fetchAndRenderLiveSlots(garageId) {
try {
const response = await fetch(`/api/get_garage_slots?garage_id=${garageId}`);
const data = await response.json();
if (data.status === 'success') {
const dbSlots = data.slots; // The dictionary coming from Supabase
// Dynamically update the status in your existing local parkingSlotsData array
for (const category in parkingSlotsData) {
parkingSlotsData[category].forEach(slot => {
if (dbSlots[slot.id]) {
// If DB says 'available', set it to 'available', otherwise mark as 'occupied'
slot.status = dbSlots[slot.id] === 'available' ? 'available' : 'occupied';
}
});
}
// Render the layout perfectly with the freshly updated live database data
renderParkingLayout();
}
} catch (error) {
console.error("Error fetching live slot data from server:", error);
}
}