332 lines
9.9 KiB
JavaScript
332 lines
9.9 KiB
JavaScript
const MAP_CONFIG = {
|
|
totalSpots: 10,
|
|
updateInterval: 3000
|
|
};
|
|
|
|
let spotsState = {
|
|
spots: [],
|
|
selectedSpot: null
|
|
};
|
|
|
|
const SPOT_STATUS = { FREE: 'free', OCCUPIED: 'occupied', RESERVED: 'reserved' };
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('🗺️ Initialisation de la carte...');
|
|
initParkingMap();
|
|
});
|
|
|
|
async function initParkingMap() {
|
|
const loaded = await loadSpotsFromAPI();
|
|
if (!loaded) loadSpotsFromStorage();
|
|
|
|
renderMap();
|
|
updateStats();
|
|
updateReservationForm();
|
|
startAPIPolling();
|
|
}
|
|
|
|
|
|
|
|
async function loadSpotsFromAPI() {
|
|
try {
|
|
const token = localStorage.getItem('smart_parking_token');
|
|
if (!token) return false;
|
|
|
|
const response = await fetch('/api/spots', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
});
|
|
|
|
if (!response.ok) return false;
|
|
|
|
const data = await response.json();
|
|
if (!data.success || !data.data.length) return false;
|
|
|
|
spotsState.spots = data.data.map(s => ({
|
|
id: s.id,
|
|
number: s.number,
|
|
status: s.status,
|
|
lastUpdate: s.last_update,
|
|
sensorId: s.sensor_id
|
|
}));
|
|
|
|
saveSpots();
|
|
return true;
|
|
|
|
} catch (_err) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function loadSpotsFromStorage() {
|
|
const stored = localStorage.getItem('smart_parking_spots');
|
|
if (stored) {
|
|
spotsState.spots = JSON.parse(stored);
|
|
} else {
|
|
createDefaultSpots();
|
|
}
|
|
}
|
|
|
|
function createDefaultSpots() {
|
|
spotsState.spots = [];
|
|
for (let i = 1; i <= MAP_CONFIG.totalSpots; i++) {
|
|
spotsState.spots.push({
|
|
id: i,
|
|
number: i,
|
|
status: SPOT_STATUS.FREE,
|
|
lastUpdate: new Date().toISOString(),
|
|
sensorId: `SENSOR_${String(i).padStart(3, '0')}`
|
|
});
|
|
}
|
|
saveSpots();
|
|
}
|
|
|
|
function saveSpots() {
|
|
localStorage.setItem('smart_parking_spots', JSON.stringify(spotsState.spots));
|
|
}
|
|
|
|
|
|
function startAPIPolling() {
|
|
setInterval(async () => {
|
|
const loaded = await loadSpotsFromAPI();
|
|
if (loaded) {
|
|
renderMap();
|
|
updateStats();
|
|
|
|
|
|
const resSpot = document.getElementById('resSpot');
|
|
const hasSelection = resSpot && resSpot.value !== '';
|
|
if (!hasSelection) {
|
|
updateReservationForm();
|
|
}
|
|
|
|
|
|
if (spotsState.selectedSpot) {
|
|
const updated = spotsState.spots.find(s => s.id === spotsState.selectedSpot.id);
|
|
if (updated) {
|
|
spotsState.selectedSpot = updated;
|
|
showSpotDetails(updated);
|
|
}
|
|
}
|
|
}
|
|
}, MAP_CONFIG.updateInterval);
|
|
}
|
|
|
|
|
|
function renderMap() {
|
|
const mapContainer = document.getElementById('parkingMap');
|
|
if (!mapContainer) return;
|
|
|
|
mapContainer.innerHTML = spotsState.spots.map(spot => `
|
|
<div
|
|
class="parking-spot ${spot.status}"
|
|
data-id="${spot.id}"
|
|
onclick="handleSpotClick(${spot.id})"
|
|
title="Place ${spot.number} — ${getStatusLabel(spot.status)}"
|
|
>
|
|
<span class="spot-number">${spot.number}</span>
|
|
<span class="spot-icon">${getStatusIcon(spot.status)}</span>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function handleSpotClick(spotId) {
|
|
const spot = spotsState.spots.find(s => s.id === spotId);
|
|
if (!spot) return;
|
|
spotsState.selectedSpot = spot;
|
|
showSpotDetails(spot);
|
|
}
|
|
|
|
function showSpotDetails(spot) {
|
|
const container = document.getElementById('spotDetails');
|
|
if (!container) return;
|
|
|
|
const reservation = spot.status === SPOT_STATUS.RESERVED
|
|
? findReservationForSpot(spot.id)
|
|
: null;
|
|
|
|
container.innerHTML = `
|
|
<div class="spot-info-detail">
|
|
<div class="spot-info-row">
|
|
<span class="spot-info-label">Numéro</span>
|
|
<span class="spot-info-value">Place ${spot.number}</span>
|
|
</div>
|
|
<div class="spot-info-row">
|
|
<span class="spot-info-label">État</span>
|
|
<span class="spot-info-value spot-status-${spot.status}">
|
|
${getStatusLabel(spot.status)}
|
|
</span>
|
|
</div>
|
|
<div class="spot-info-row">
|
|
<span class="spot-info-label">Capteur</span>
|
|
<span class="spot-info-value">${spot.sensorId || 'N/A'}</span>
|
|
</div>
|
|
<div class="spot-info-row">
|
|
<span class="spot-info-label">Dernière mise à jour</span>
|
|
<span class="spot-info-value">${formatDate(spot.lastUpdate)}</span>
|
|
</div>
|
|
${reservation ? `
|
|
<div class="spot-info-row">
|
|
<span class="spot-info-label">Réservé jusqu'à</span>
|
|
<span class="spot-info-value">${reservation.endTime}</span>
|
|
</div>
|
|
` : ''}
|
|
${spot.status !== SPOT_STATUS.OCCUPIED ? `
|
|
<button class="btn btn-primary btn-block"
|
|
onclick="Dashboard.navigateToPage('reservation'); selectSpotForReservation(${spot.id});">
|
|
<span class="btn-icon">📅</span>
|
|
Réserver cette place
|
|
</button>
|
|
` : `
|
|
<p style="color: var(--danger); text-align: center; margin-top: 12px;">
|
|
🚗 Une voiture est physiquement sur cette place
|
|
</p>
|
|
`}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function findReservationForSpot(spotId) {
|
|
const reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
|
return reservations.find(r => r.spotId === spotId && r.status === 'active');
|
|
}
|
|
|
|
|
|
function updateStats() {
|
|
const free = spotsState.spots.filter(s => s.status === SPOT_STATUS.FREE).length;
|
|
const occupied = spotsState.spots.filter(s => s.status === SPOT_STATUS.OCCUPIED).length;
|
|
const reserved = spotsState.spots.filter(s => s.status === SPOT_STATUS.RESERVED).length;
|
|
|
|
document.getElementById('freeCount').textContent = free;
|
|
document.getElementById('occupiedCount').textContent = occupied;
|
|
document.getElementById('reservedCount').textContent = reserved;
|
|
document.getElementById('totalCount').textContent = spotsState.spots.length;
|
|
}
|
|
|
|
|
|
|
|
function updateReservationForm() {
|
|
const select = document.getElementById('resSpot');
|
|
if (!select) return;
|
|
|
|
|
|
const currentValue = select.value;
|
|
|
|
const firstOption = select.options[0];
|
|
select.innerHTML = '';
|
|
select.appendChild(firstOption);
|
|
|
|
|
|
const availableSpots = spotsState.spots.filter(s => s.status !== SPOT_STATUS.OCCUPIED);
|
|
|
|
availableSpots.forEach(spot => {
|
|
const option = document.createElement('option');
|
|
option.value = spot.id;
|
|
|
|
option.textContent = spot.status === SPOT_STATUS.RESERVED
|
|
? `Place ${spot.number} (réservée en ce moment)`
|
|
: `Place ${spot.number}`;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
if (currentValue) {
|
|
select.value = currentValue;
|
|
}
|
|
}
|
|
|
|
function selectSpotForReservation(spotId) {
|
|
const select = document.getElementById('resSpot');
|
|
if (select) select.value = spotId;
|
|
}
|
|
|
|
|
|
|
|
async function setSpotStatus(spotId, status) {
|
|
const spot = spotsState.spots.find(s => s.id === spotId || s.number === spotId);
|
|
if (!spot) return;
|
|
|
|
spot.status = status;
|
|
spot.lastUpdate = new Date().toISOString();
|
|
saveSpots();
|
|
renderMap();
|
|
updateStats();
|
|
updateReservationForm();
|
|
|
|
try {
|
|
const token = localStorage.getItem('smart_parking_token');
|
|
if (token) {
|
|
await fetch(`/api/spots/${spot.id}/status`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': 'Bearer ' + token
|
|
},
|
|
body: JSON.stringify({ status })
|
|
});
|
|
}
|
|
} catch (_err) { /* mode offline */ }
|
|
}
|
|
|
|
function setTotalSpots(count) {
|
|
MAP_CONFIG.totalSpots = count;
|
|
if (count > spotsState.spots.length) {
|
|
for (let i = spotsState.spots.length + 1; i <= count; i++) {
|
|
spotsState.spots.push({
|
|
id: i, number: i,
|
|
status: SPOT_STATUS.FREE,
|
|
lastUpdate: new Date().toISOString(),
|
|
sensorId: `SENSOR_${String(i).padStart(3, '0')}`
|
|
});
|
|
}
|
|
} else {
|
|
spotsState.spots = spotsState.spots.slice(0, count);
|
|
}
|
|
saveSpots();
|
|
renderMap();
|
|
updateStats();
|
|
updateReservationForm();
|
|
}
|
|
|
|
|
|
function isAdmin() {
|
|
const user = JSON.parse(localStorage.getItem('smart_parking_user') || 'null');
|
|
return user && user.role === 'admin';
|
|
}
|
|
|
|
function getStatusLabel(status) {
|
|
return { free: 'Libre', occupied: 'Occupée', reserved: 'Réservée' }[status] || 'Inconnu';
|
|
}
|
|
|
|
function getStatusIcon(status) {
|
|
return { free: '✓', occupied: '🚗', reserved: '📅' }[status] || '?';
|
|
}
|
|
|
|
function formatDate(dateString) {
|
|
if (!dateString) return 'N/A';
|
|
return new Date(dateString).toLocaleString('fr-FR', {
|
|
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
hour: '2-digit', minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
|
|
window.ParkingMap = {
|
|
refresh: async () => {
|
|
const loaded = await loadSpotsFromAPI();
|
|
if (!loaded) loadSpotsFromStorage();
|
|
renderMap();
|
|
updateStats();
|
|
updateReservationForm();
|
|
},
|
|
setSpotStatus,
|
|
setTotalSpots,
|
|
getSpots: () => spotsState.spots,
|
|
getFreeSpots: () => spotsState.spots.filter(s => s.status === SPOT_STATUS.FREE),
|
|
getStats: () => ({
|
|
total: spotsState.spots.length,
|
|
free: spotsState.spots.filter(s => s.status === SPOT_STATUS.FREE).length,
|
|
occupied: spotsState.spots.filter(s => s.status === SPOT_STATUS.OCCUPIED).length,
|
|
reserved: spotsState.spots.filter(s => s.status === SPOT_STATUS.RESERVED).length
|
|
})
|
|
}; |