Mise à jour
This commit is contained in:
134
js/admin.js
134
js/admin.js
@@ -1,14 +1,3 @@
|
||||
/**
|
||||
* ============================================
|
||||
* ADMIN.JS - Panel d'administration
|
||||
* Smart Parking v3.0
|
||||
* CORRIGÉ :
|
||||
* - Suppression du graphique d'occupation
|
||||
* - Historique affiche la date complète
|
||||
* (jour + mois + année + heure + minute)
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('⚙️ Initialisation du panel admin...');
|
||||
if (!isAdmin()) return;
|
||||
@@ -27,7 +16,6 @@ function initAdminPanel() {
|
||||
loadReservationsTable();
|
||||
loadHistoryLog();
|
||||
|
||||
// Rafraîchissement périodique toutes les 10 secondes
|
||||
setInterval(() => {
|
||||
loadAdminStats();
|
||||
loadReservationsTable();
|
||||
@@ -35,9 +23,6 @@ function initAdminPanel() {
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// STATISTIQUES ADMIN
|
||||
// ============================================
|
||||
|
||||
function loadAdminStats() {
|
||||
const users = JSON.parse(localStorage.getItem('smart_parking_users') || '[]');
|
||||
@@ -62,21 +47,44 @@ function loadAdminStats() {
|
||||
document.getElementById('adminOccupancyRate').textContent = occupancyRate + '%';
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GESTION DES PLACES
|
||||
// ============================================
|
||||
|
||||
function initPlacesControl() {
|
||||
const spots = JSON.parse(localStorage.getItem('smart_parking_spots') || '[]');
|
||||
const spotsInput = document.getElementById('adminTotalSpots');
|
||||
if (spotsInput) spotsInput.value = spots.length || 10;
|
||||
|
||||
document.getElementById('updateSpotsBtn')?.addEventListener('click', () => {
|
||||
document.getElementById('updateSpotsBtn')?.addEventListener('click', async () => {
|
||||
const newCount = parseInt(document.getElementById('adminTotalSpots').value);
|
||||
if (newCount < 5 || newCount > 50) {
|
||||
Dashboard.showToast('Le nombre de places doit être entre 5 et 50', 'error');
|
||||
|
||||
if (newCount < 1 || newCount > 20) {
|
||||
Dashboard.showToast('Le nombre de places doit être entre 1 et 20', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
if (token) {
|
||||
try {
|
||||
const response = await fetch('/api/spots/init', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
body: JSON.stringify({ count: newCount })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
|
||||
if (window.ParkingMap) await window.ParkingMap.refresh();
|
||||
renderAdminPlacesList();
|
||||
Dashboard.showToast('Nombre de places mis à jour', 'success');
|
||||
return;
|
||||
}
|
||||
} catch (_err) { /* fallback local */ }
|
||||
}
|
||||
|
||||
|
||||
if (window.ParkingMap) {
|
||||
window.ParkingMap.setTotalSpots(newCount);
|
||||
renderAdminPlacesList();
|
||||
@@ -104,27 +112,48 @@ function renderAdminPlacesList() {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function toggleSpotStatus(spotId) {
|
||||
|
||||
async function toggleSpotStatus(spotId) {
|
||||
const spots = JSON.parse(localStorage.getItem('smart_parking_spots') || '[]');
|
||||
const spot = spots.find(s => s.id === spotId);
|
||||
if (!spot) return;
|
||||
|
||||
const cycle = ['free', 'occupied', 'reserved'];
|
||||
const nextStatus = cycle[(cycle.indexOf(spot.status) + 1) % cycle.length];
|
||||
spot.status = nextStatus;
|
||||
spot.lastUpdate = new Date().toISOString();
|
||||
const cycle = ['free', 'occupied', 'reserved'];
|
||||
const nextStatus = cycle[(cycle.indexOf(spot.status) + 1) % cycle.length];
|
||||
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
if (token) {
|
||||
try {
|
||||
const response = await fetch(`/api/spots/${spot.id}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
body: JSON.stringify({ status: nextStatus })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!data.success) {
|
||||
Dashboard.showToast('Erreur : ' + data.message, 'error');
|
||||
return;
|
||||
}
|
||||
} catch (_err) {
|
||||
console.warn('⚠️ API indisponible, modification locale uniquement');
|
||||
}
|
||||
}
|
||||
|
||||
spot.status = nextStatus;
|
||||
spot.lastUpdate = new Date().toISOString();
|
||||
localStorage.setItem('smart_parking_spots', JSON.stringify(spots));
|
||||
|
||||
renderAdminPlacesList();
|
||||
if (window.ParkingMap) window.ParkingMap.refresh();
|
||||
loadAdminStats();
|
||||
|
||||
Dashboard.showToast(`Place ${spot.number} — ${getStatusLabel(nextStatus)}`, 'success');
|
||||
Dashboard.showToast(`Place ${spot.number} → ${getStatusLabel(nextStatus)}`, 'success');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TABLEAU UTILISATEURS
|
||||
// ============================================
|
||||
|
||||
|
||||
function loadUsersTable() {
|
||||
const tbody = document.getElementById('adminUsersTable');
|
||||
@@ -176,9 +205,7 @@ function deleteUser(userId) {
|
||||
Dashboard.showToast('Utilisateur supprimé', 'success');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TABLEAU RÉSERVATIONS
|
||||
// ============================================
|
||||
|
||||
|
||||
function loadReservationsTable() {
|
||||
const tbody = document.getElementById('adminReservationsTable');
|
||||
@@ -218,11 +245,22 @@ function loadReservationsTable() {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function completeReservation(reservationId) {
|
||||
async function completeReservation(reservationId) {
|
||||
let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
const reservation = reservations.find(r => r.id === reservationId);
|
||||
if (!reservation) return;
|
||||
|
||||
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
if (token) {
|
||||
try {
|
||||
await fetch(`/api/reservations/${reservationId}/complete`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
} catch (_err) { /* fallback local */ }
|
||||
}
|
||||
|
||||
reservation.status = 'completed';
|
||||
localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations));
|
||||
|
||||
@@ -233,13 +271,24 @@ function completeReservation(reservationId) {
|
||||
Dashboard.showToast('Réservation terminée', 'success');
|
||||
}
|
||||
|
||||
function adminCancelReservation(reservationId) {
|
||||
async function adminCancelReservation(reservationId) {
|
||||
if (!confirm('Êtes-vous sûr de vouloir annuler cette réservation ?')) return;
|
||||
|
||||
let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
const reservation = reservations.find(r => r.id === reservationId);
|
||||
if (!reservation) return;
|
||||
|
||||
// Appeler l'API
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
if (token) {
|
||||
try {
|
||||
await fetch(`/api/reservations/${reservationId}/cancel`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Authorization': 'Bearer ' + token }
|
||||
});
|
||||
} catch (_err) { /* fallback local */ }
|
||||
}
|
||||
|
||||
reservation.status = 'cancelled';
|
||||
localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations));
|
||||
|
||||
@@ -250,9 +299,6 @@ function adminCancelReservation(reservationId) {
|
||||
Dashboard.showToast('Réservation annulée', 'success');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// HISTORIQUE — CORRIGÉ : date complète
|
||||
// ============================================
|
||||
|
||||
function loadHistoryLog() {
|
||||
const container = document.getElementById('adminLogContainer');
|
||||
@@ -273,15 +319,8 @@ function loadHistoryLog() {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// FONCTIONS DE FORMAT DATE
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* CORRIGÉ — Affiche la date complète dans l'historique
|
||||
* Avant : seulement "14:32"
|
||||
* Après : "12/06/2025 à 14:32"
|
||||
*/
|
||||
|
||||
function formatDateComplete(dateString) {
|
||||
if (!dateString) return 'N/A';
|
||||
const date = new Date(dateString);
|
||||
@@ -312,9 +351,6 @@ function getStatusLabel(status) {
|
||||
}[status] || status;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// EXPORT
|
||||
// ============================================
|
||||
|
||||
window.AdminModule = {
|
||||
refresh: () => {
|
||||
|
||||
156
js/auth.js
156
js/auth.js
@@ -1,57 +1,33 @@
|
||||
/**
|
||||
* ============================================
|
||||
* AUTH.JS - Système d'authentification
|
||||
* Smart Parking - BTS CIEL IR
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
// Configuration
|
||||
const AUTH_CONFIG = {
|
||||
apiUrl: 'http://localhost:3000/api',
|
||||
apiUrl: '/api',
|
||||
tokenKey: 'smart_parking_token',
|
||||
userKey: 'smart_parking_user'
|
||||
};
|
||||
|
||||
// État de l'authentification
|
||||
let authState = {
|
||||
isLoggedIn: false,
|
||||
user: null,
|
||||
token: null
|
||||
};
|
||||
|
||||
// Initialisation
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('🔐 Initialisation du système d\'authentification...');
|
||||
|
||||
// Vérifier si déjà connecté
|
||||
checkAuthStatus();
|
||||
|
||||
// Initialiser les écouteurs d'événements
|
||||
initEventListeners();
|
||||
});
|
||||
|
||||
/**
|
||||
* Vérifie le statut d'authentification
|
||||
*/
|
||||
function checkAuthStatus() {
|
||||
const token = localStorage.getItem(AUTH_CONFIG.tokenKey);
|
||||
const user = localStorage.getItem(AUTH_CONFIG.userKey);
|
||||
|
||||
if (token && user) {
|
||||
authState.token = token;
|
||||
authState.user = JSON.parse(user);
|
||||
authState.isLoggedIn = true;
|
||||
|
||||
// Rediriger vers le dashboard
|
||||
window.location.href = 'pages/dashboard.html';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise les écouteurs d'événements
|
||||
*/
|
||||
function initEventListeners() {
|
||||
// Basculer vers l'inscription
|
||||
const showRegister = document.getElementById('showRegister');
|
||||
if (showRegister) {
|
||||
showRegister.addEventListener('click', (e) => {
|
||||
@@ -59,8 +35,7 @@ function initEventListeners() {
|
||||
showRegisterForm();
|
||||
});
|
||||
}
|
||||
|
||||
// Basculer vers la connexion
|
||||
|
||||
const showLogin = document.getElementById('showLogin');
|
||||
if (showLogin) {
|
||||
showLogin.addEventListener('click', (e) => {
|
||||
@@ -68,172 +43,121 @@ function initEventListeners() {
|
||||
showLoginForm();
|
||||
});
|
||||
}
|
||||
|
||||
// Formulaire de connexion
|
||||
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', handleLogin);
|
||||
}
|
||||
|
||||
// Formulaire d'inscription
|
||||
if (loginForm) loginForm.addEventListener('submit', handleLogin);
|
||||
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
if (registerForm) {
|
||||
registerForm.addEventListener('submit', handleRegister);
|
||||
}
|
||||
if (registerForm) registerForm.addEventListener('submit', handleRegister);
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le formulaire d'inscription
|
||||
*/
|
||||
function showRegisterForm() {
|
||||
document.getElementById('loginForm').classList.add('hidden');
|
||||
document.getElementById('registerForm').classList.remove('hidden');
|
||||
hideMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche le formulaire de connexion
|
||||
*/
|
||||
function showLoginForm() {
|
||||
document.getElementById('registerForm').classList.add('hidden');
|
||||
document.getElementById('loginForm').classList.remove('hidden');
|
||||
hideMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère la connexion
|
||||
*/
|
||||
async function handleLogin(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('loginEmail').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
|
||||
|
||||
if (!email || !password) {
|
||||
showMessage('Veuillez remplir tous les champs', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Appel API de connexion
|
||||
const response = await fetch(`${AUTH_CONFIG.apiUrl}/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
if (data.success) {
|
||||
// Stocker les informations
|
||||
localStorage.setItem(AUTH_CONFIG.tokenKey, data.token);
|
||||
localStorage.setItem(AUTH_CONFIG.userKey, JSON.stringify(data.user));
|
||||
|
||||
showMessage('Connexion réussie ! Redirection...', 'success');
|
||||
|
||||
// Rediriger vers le dashboard
|
||||
setTimeout(() => {
|
||||
window.location.href = 'pages/dashboard.html';
|
||||
}, 1000);
|
||||
setTimeout(() => { window.location.href = 'pages/dashboard.html'; }, 1000);
|
||||
} else {
|
||||
showMessage(data.message || 'Email ou mot de passe incorrect', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur connexion:', error);
|
||||
// Mode offline - simulation
|
||||
handleOfflineLogin(email, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère la connexion en mode offline (simulation)
|
||||
*/
|
||||
function handleOfflineLogin(email, password) {
|
||||
// Vérifier dans les utilisateurs stockés localement
|
||||
const users = JSON.parse(localStorage.getItem('smart_parking_users') || '[]');
|
||||
const user = users.find(u => u.email === email && u.password === password);
|
||||
|
||||
|
||||
if (user) {
|
||||
const token = generateToken();
|
||||
localStorage.setItem(AUTH_CONFIG.tokenKey, token);
|
||||
localStorage.setItem(AUTH_CONFIG.userKey, JSON.stringify(user));
|
||||
|
||||
showMessage('Connexion réussie ! Redirection...', 'success');
|
||||
setTimeout(() => {
|
||||
window.location.href = 'pages/dashboard.html';
|
||||
}, 1000);
|
||||
setTimeout(() => { window.location.href = 'pages/dashboard.html'; }, 1000);
|
||||
} else {
|
||||
// Compte admin par défaut
|
||||
if (email === 'admin@smartparking.fr' && password === 'admin123') {
|
||||
const adminUser = {
|
||||
id: 1,
|
||||
name: 'Administrateur',
|
||||
id: 1, name: 'Administrateur',
|
||||
email: 'admin@smartparking.fr',
|
||||
phone: '01 23 45 67 89',
|
||||
role: 'admin'
|
||||
phone: '01 23 45 67 89', role: 'admin'
|
||||
};
|
||||
const token = generateToken();
|
||||
localStorage.setItem(AUTH_CONFIG.tokenKey, token);
|
||||
localStorage.setItem(AUTH_CONFIG.userKey, JSON.stringify(adminUser));
|
||||
|
||||
showMessage('Connexion admin réussie ! Redirection...', 'success');
|
||||
setTimeout(() => {
|
||||
window.location.href = 'pages/dashboard.html';
|
||||
}, 1000);
|
||||
setTimeout(() => { window.location.href = 'pages/dashboard.html'; }, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage('Email ou mot de passe incorrect', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère l'inscription
|
||||
*/
|
||||
async function handleRegister(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const name = document.getElementById('registerName').value;
|
||||
const email = document.getElementById('registerEmail').value;
|
||||
const phone = document.getElementById('registerPhone').value;
|
||||
const password = document.getElementById('registerPassword').value;
|
||||
const passwordConfirm = document.getElementById('registerPasswordConfirm').value;
|
||||
|
||||
// Validation
|
||||
|
||||
if (!name || !email || !phone || !password) {
|
||||
showMessage('Veuillez remplir tous les champs', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== passwordConfirm) {
|
||||
showMessage('Les mots de passe ne correspondent pas', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
showMessage('Le mot de passe doit faire au moins 8 caractères', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Appel API d'inscription
|
||||
const response = await fetch(`${AUTH_CONFIG.apiUrl}/register`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, email, phone, password })
|
||||
});
|
||||
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
if (data.success) {
|
||||
showMessage('Compte créé avec succès ! Vous pouvez vous connecter.', 'success');
|
||||
setTimeout(() => {
|
||||
showLoginForm();
|
||||
// Pré-remplir l'email
|
||||
document.getElementById('loginEmail').value = email;
|
||||
}, 1500);
|
||||
} else {
|
||||
@@ -241,39 +165,22 @@ async function handleRegister(e) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur inscription:', error);
|
||||
// Mode offline - stockage local
|
||||
handleOfflineRegister(name, email, phone, password);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère l'inscription en mode offline (simulation)
|
||||
*/
|
||||
function handleOfflineRegister(name, email, phone, password) {
|
||||
// Récupérer les utilisateurs existants
|
||||
let users = JSON.parse(localStorage.getItem('smart_parking_users') || '[]');
|
||||
|
||||
// Vérifier si l'email existe déjà
|
||||
if (users.find(u => u.email === email)) {
|
||||
showMessage('Cet email est déjà utilisé', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Créer le nouvel utilisateur
|
||||
const newUser = {
|
||||
id: Date.now(),
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
password, // En production: hasher le mot de passe !
|
||||
role: 'client',
|
||||
createdAt: new Date().toISOString()
|
||||
id: Date.now(), name, email, phone, password,
|
||||
role: 'client', createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Ajouter à la liste
|
||||
users.push(newUser);
|
||||
localStorage.setItem('smart_parking_users', JSON.stringify(users));
|
||||
|
||||
showMessage('Compte créé avec succès ! Vous pouvez vous connecter.', 'success');
|
||||
setTimeout(() => {
|
||||
showLoginForm();
|
||||
@@ -281,16 +188,10 @@ function handleOfflineRegister(name, email, phone, password) {
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un token simple
|
||||
*/
|
||||
function generateToken() {
|
||||
return 'token_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message
|
||||
*/
|
||||
function showMessage(message, type) {
|
||||
const messageEl = document.getElementById('authMessage');
|
||||
messageEl.textContent = message;
|
||||
@@ -298,24 +199,17 @@ function showMessage(message, type) {
|
||||
messageEl.classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache le message
|
||||
*/
|
||||
function hideMessage() {
|
||||
const messageEl = document.getElementById('authMessage');
|
||||
messageEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnexion
|
||||
*/
|
||||
function logout() {
|
||||
localStorage.removeItem(AUTH_CONFIG.tokenKey);
|
||||
localStorage.removeItem(AUTH_CONFIG.userKey);
|
||||
window.location.href = '../index.html';
|
||||
}
|
||||
|
||||
// Exporter les fonctions
|
||||
window.Auth = {
|
||||
logout,
|
||||
getToken: () => localStorage.getItem(AUTH_CONFIG.tokenKey),
|
||||
@@ -324,4 +218,4 @@ window.Auth = {
|
||||
const user = JSON.parse(localStorage.getItem(AUTH_CONFIG.userKey) || 'null');
|
||||
return user && user.role === 'admin';
|
||||
}
|
||||
};
|
||||
};
|
||||
115
js/dashboard.js
115
js/dashboard.js
@@ -1,13 +1,4 @@
|
||||
/**
|
||||
* ============================================
|
||||
* DASHBOARD.JS - Gestion du dashboard
|
||||
* Smart Parking - BTS CIEL IR
|
||||
* CORRIGÉ : cancelReservation utilisait spotNumber
|
||||
* au lieu de spotId pour libérer la place
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
const API_URL = 'http://localhost:3000/api';
|
||||
const API_URL = '/api';
|
||||
|
||||
let dashboardState = {
|
||||
user: null,
|
||||
@@ -93,10 +84,10 @@ function loadUserData() {
|
||||
document.getElementById('userName').textContent = user.name;
|
||||
document.getElementById('userRole').textContent = user.role === 'admin' ? 'Administrateur' : 'Client';
|
||||
|
||||
document.getElementById('profileName').textContent = user.name;
|
||||
document.getElementById('profileNameInput').value = user.name;
|
||||
document.getElementById('profileEmailInput').value = user.email;
|
||||
document.getElementById('profilePhoneInput').value = user.phone || '';
|
||||
document.getElementById('profileName').textContent = user.name;
|
||||
document.getElementById('profileNameInput').value = user.name;
|
||||
document.getElementById('profileEmailInput').value = user.email;
|
||||
document.getElementById('profilePhoneInput').value = user.phone || '';
|
||||
|
||||
const roleBadge = document.getElementById('profileRole');
|
||||
if (roleBadge) {
|
||||
@@ -137,7 +128,7 @@ function loadMyReservations() {
|
||||
<h4>Place ${res.spotNumber}</h4>
|
||||
<div class="reservation-details">
|
||||
<span>📅 ${res.date}</span>
|
||||
<span>🕐 ${res.startTime}</span>
|
||||
<span>🕐 ${res.startTime} - ${res.endTime}</span>
|
||||
<span>⏱️ ${formatDurationLabel(res.duration)}</span>
|
||||
<span>🚗 ${res.vehicle || 'N/A'}</span>
|
||||
</div>
|
||||
@@ -155,22 +146,16 @@ function loadMyReservations() {
|
||||
`).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* CORRIGÉ : utilisait reservation.spotNumber (le numéro visible)
|
||||
* au lieu de reservation.spotId (l'id interne), ce qui empêchait
|
||||
* la place d'être libérée sur la carte.
|
||||
*/
|
||||
function cancelReservation(reservationId) {
|
||||
if (!confirm('Êtes-vous sûr de vouloir annuler cette réservation ?')) return;
|
||||
|
||||
let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
const reservation = reservations.find(r => r.id === reservationId);
|
||||
|
||||
if (reservation) {
|
||||
reservation.status = 'cancelled';
|
||||
localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations));
|
||||
|
||||
// CORRIGÉ : spotId au lieu de spotNumber
|
||||
if (window.ParkingMap) {
|
||||
window.ParkingMap.setSpotStatus(reservation.spotId, 'free');
|
||||
}
|
||||
@@ -181,26 +166,82 @@ function cancelReservation(reservationId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mise à jour du profil
|
||||
document.getElementById('profileForm')?.addEventListener('submit', (e) => {
|
||||
document.getElementById('profileForm')?.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const phone = document.getElementById('profilePhoneInput').value;
|
||||
const newPassword = document.getElementById('profileNewPassword').value;
|
||||
const confirmPass = document.getElementById('profileNewPassword').value;
|
||||
|
||||
let user = dashboardState.user;
|
||||
user.phone = phone;
|
||||
if (newPassword) user.password = newPassword;
|
||||
|
||||
localStorage.setItem('smart_parking_user', JSON.stringify(user));
|
||||
|
||||
let users = JSON.parse(localStorage.getItem('smart_parking_users') || '[]');
|
||||
const idx = users.findIndex(u => u.id === user.id);
|
||||
if (idx !== -1) {
|
||||
users[idx] = user;
|
||||
localStorage.setItem('smart_parking_users', JSON.stringify(users));
|
||||
|
||||
if (newPassword && newPassword.length < 8) {
|
||||
showToast('Le mot de passe doit faire au moins 8 caractères', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showToast('Profil mis à jour', 'success');
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
|
||||
const updateData = { phone };
|
||||
if (newPassword) {
|
||||
updateData.password = newPassword;
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(`${API_URL}/users/profile`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
body: JSON.stringify(updateData)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
|
||||
let user = dashboardState.user;
|
||||
user.phone = phone;
|
||||
dashboardState.user = user;
|
||||
localStorage.setItem('smart_parking_user', JSON.stringify(user));
|
||||
|
||||
|
||||
document.getElementById('profileNewPassword').value = '';
|
||||
|
||||
showToast('Profil mis à jour avec succès !', 'success');
|
||||
} else {
|
||||
showToast(data.message || 'Erreur lors de la mise à jour', 'error');
|
||||
}
|
||||
|
||||
} catch (_err) {
|
||||
|
||||
let user = dashboardState.user;
|
||||
user.phone = phone;
|
||||
if (newPassword) user.password = newPassword;
|
||||
dashboardState.user = user;
|
||||
localStorage.setItem('smart_parking_user', JSON.stringify(user));
|
||||
|
||||
let users = JSON.parse(localStorage.getItem('smart_parking_users') || '[]');
|
||||
const idx = users.findIndex(u => u.id === user.id);
|
||||
if (idx !== -1) {
|
||||
users[idx] = user;
|
||||
localStorage.setItem('smart_parking_users', JSON.stringify(users));
|
||||
}
|
||||
|
||||
document.getElementById('profileNewPassword').value = '';
|
||||
showToast('Profil mis à jour', 'success');
|
||||
}
|
||||
} else {
|
||||
let user = dashboardState.user;
|
||||
user.phone = phone;
|
||||
if (newPassword) user.password = newPassword;
|
||||
dashboardState.user = user;
|
||||
localStorage.setItem('smart_parking_user', JSON.stringify(user));
|
||||
showToast('Profil mis à jour', 'success');
|
||||
}
|
||||
});
|
||||
|
||||
function getStatusLabel(status) {
|
||||
@@ -208,7 +249,7 @@ function getStatusLabel(status) {
|
||||
}
|
||||
|
||||
function formatDurationLabel(minutes) {
|
||||
if (minutes >= 480) return 'Journée';
|
||||
if (minutes >= 480) return 'Journée (8h)';
|
||||
if (minutes >= 60) return Math.floor(minutes / 60) + 'h' + (minutes % 60 ? (minutes % 60) + 'min' : '');
|
||||
return minutes + ' min';
|
||||
}
|
||||
|
||||
117
js/map.js
117
js/map.js
@@ -1,15 +1,6 @@
|
||||
/**
|
||||
* ============================================
|
||||
* MAP.JS - Carte des places de parking
|
||||
* Smart Parking v2.0
|
||||
* MODIFIÉ : polling API toutes les 3s pour recevoir
|
||||
* les mises à jour en temps réel depuis Arduino
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
const MAP_CONFIG = {
|
||||
totalSpots: 10,
|
||||
updateInterval: 3000 // Refresh depuis l'API toutes les 3 secondes
|
||||
updateInterval: 3000
|
||||
};
|
||||
|
||||
let spotsState = {
|
||||
@@ -19,9 +10,7 @@ let spotsState = {
|
||||
|
||||
const SPOT_STATUS = { FREE: 'free', OCCUPIED: 'occupied', RESERVED: 'reserved' };
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION
|
||||
// ============================================
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('🗺️ Initialisation de la carte...');
|
||||
@@ -29,30 +18,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
async function initParkingMap() {
|
||||
// Essayer d'abord de charger depuis l'API (données MariaDB + Arduino)
|
||||
const loaded = await loadSpotsFromAPI();
|
||||
|
||||
// Si pas d'API disponible, utiliser le localStorage (mode offline)
|
||||
if (!loaded) {
|
||||
loadSpotsFromStorage();
|
||||
}
|
||||
if (!loaded) loadSpotsFromStorage();
|
||||
|
||||
renderMap();
|
||||
updateStats();
|
||||
updateReservationForm();
|
||||
|
||||
// ⭐ Polling toutes les 3 secondes pour recevoir les updates Arduino
|
||||
startAPIPolling();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CHARGEMENT DES PLACES
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Charge les places depuis l'API (MariaDB)
|
||||
* Retourne true si succès, false si hors-ligne
|
||||
*/
|
||||
|
||||
async function loadSpotsFromAPI() {
|
||||
try {
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
@@ -67,7 +43,6 @@ async function loadSpotsFromAPI() {
|
||||
const data = await response.json();
|
||||
if (!data.success || !data.data.length) return false;
|
||||
|
||||
// Convertir le format API → format interne
|
||||
spotsState.spots = data.data.map(s => ({
|
||||
id: s.id,
|
||||
number: s.number,
|
||||
@@ -76,19 +51,14 @@ async function loadSpotsFromAPI() {
|
||||
sensorId: s.sensor_id
|
||||
}));
|
||||
|
||||
// Synchroniser avec localStorage pour le mode offline
|
||||
saveSpots();
|
||||
return true;
|
||||
|
||||
} catch (_err) {
|
||||
// Serveur non joignable → mode offline
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge les places depuis le localStorage (mode offline)
|
||||
*/
|
||||
function loadSpotsFromStorage() {
|
||||
const stored = localStorage.getItem('smart_parking_spots');
|
||||
if (stored) {
|
||||
@@ -116,23 +86,22 @@ function saveSpots() {
|
||||
localStorage.setItem('smart_parking_spots', JSON.stringify(spotsState.spots));
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ⭐ POLLING TEMPS RÉEL (mises à jour Arduino)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Interroge l'API toutes les 3 secondes.
|
||||
* Si l'Arduino a changé l'état d'une place via MQTT,
|
||||
* la carte se met à jour automatiquement.
|
||||
*/
|
||||
function startAPIPolling() {
|
||||
setInterval(async () => {
|
||||
const loaded = await loadSpotsFromAPI();
|
||||
if (loaded) {
|
||||
renderMap();
|
||||
updateStats();
|
||||
updateReservationForm();
|
||||
// Rafraîchir les détails si une place est sélectionnée
|
||||
|
||||
|
||||
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) {
|
||||
@@ -144,9 +113,6 @@ function startAPIPolling() {
|
||||
}, MAP_CONFIG.updateInterval);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// RENDU DE LA CARTE
|
||||
// ============================================
|
||||
|
||||
function renderMap() {
|
||||
const mapContainer = document.getElementById('parkingMap');
|
||||
@@ -202,21 +168,21 @@ function showSpotDetails(spot) {
|
||||
</div>
|
||||
${reservation ? `
|
||||
<div class="spot-info-row">
|
||||
<span class="spot-info-label">Réservé par</span>
|
||||
<span class="spot-info-value">${reservation.userName}</span>
|
||||
</div>
|
||||
<div class="spot-info-row">
|
||||
<span class="spot-info-label">Jusqu'à</span>
|
||||
<span class="spot-info-label">Réservé jusqu'à</span>
|
||||
<span class="spot-info-value">${reservation.endTime}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${spot.status === SPOT_STATUS.FREE ? `
|
||||
${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>
|
||||
`;
|
||||
}
|
||||
@@ -226,9 +192,6 @@ function findReservationForSpot(spotId) {
|
||||
return reservations.find(r => r.spotId === spotId && r.status === 'active');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// STATISTIQUES & FORMULAIRE
|
||||
// ============================================
|
||||
|
||||
function updateStats() {
|
||||
const free = spotsState.spots.filter(s => s.status === SPOT_STATUS.FREE).length;
|
||||
@@ -241,22 +204,35 @@ function updateStats() {
|
||||
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);
|
||||
|
||||
spotsState.spots
|
||||
.filter(s => s.status === SPOT_STATUS.FREE)
|
||||
.forEach(spot => {
|
||||
const option = document.createElement('option');
|
||||
option.value = spot.id;
|
||||
option.textContent = `Place ${spot.number}`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
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) {
|
||||
@@ -264,9 +240,7 @@ function selectSpotForReservation(spotId) {
|
||||
if (select) select.value = spotId;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ACTIONS ADMIN & SIMULATION
|
||||
// ============================================
|
||||
|
||||
|
||||
async function setSpotStatus(spotId, status) {
|
||||
const spot = spotsState.spots.find(s => s.id === spotId || s.number === spotId);
|
||||
@@ -279,7 +253,6 @@ async function setSpotStatus(spotId, status) {
|
||||
updateStats();
|
||||
updateReservationForm();
|
||||
|
||||
// Synchroniser avec l'API si connecté
|
||||
try {
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
if (token) {
|
||||
@@ -315,9 +288,6 @@ function setTotalSpots(count) {
|
||||
updateReservationForm();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// UTILITAIRES
|
||||
// ============================================
|
||||
|
||||
function isAdmin() {
|
||||
const user = JSON.parse(localStorage.getItem('smart_parking_user') || 'null');
|
||||
@@ -340,9 +310,6 @@ function formatDate(dateString) {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// EXPORT
|
||||
// ============================================
|
||||
|
||||
window.ParkingMap = {
|
||||
refresh: async () => {
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
/**
|
||||
* ============================================
|
||||
* RESERVATION.JS - Système de réservation
|
||||
* Smart Parking v3.0
|
||||
* CORRIGÉ : ne bloque plus une place pour
|
||||
* toujours — vérifie uniquement si
|
||||
* une voiture est physiquement là
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
const PRICING = {
|
||||
30: 2,
|
||||
60: 3,
|
||||
@@ -27,14 +17,46 @@ const TIME_SLOTS = [
|
||||
let currentReservation = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('📅 Initialisation du système de réservation...');
|
||||
cleanupExpiredReservations();
|
||||
initReservationForm();
|
||||
initDatePicker();
|
||||
initTimeSlots();
|
||||
initPricePreview();
|
||||
initConfirmationModal();
|
||||
setInterval(cleanupExpiredReservations, 60000);
|
||||
});
|
||||
|
||||
function cleanupExpiredReservations() {
|
||||
let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
const now = new Date();
|
||||
let cleaned = 0;
|
||||
|
||||
reservations.forEach(r => {
|
||||
if (r.status !== 'active' && r.status !== 'pending') return;
|
||||
const endDateTime = new Date(r.date + 'T' + r.endTime);
|
||||
if (endDateTime < now) {
|
||||
r.status = 'completed';
|
||||
cleaned++;
|
||||
}
|
||||
});
|
||||
|
||||
if (cleaned > 0) {
|
||||
localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations));
|
||||
}
|
||||
}
|
||||
|
||||
function checkLocalConflict(spotId, date, startTime, endTime) {
|
||||
cleanupExpiredReservations();
|
||||
const reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
|
||||
return reservations.some(r => {
|
||||
if (r.spotId !== spotId) return false;
|
||||
if (r.date !== date) return false;
|
||||
if (r.status !== 'active' && r.status !== 'pending') return false;
|
||||
return r.startTime < endTime && r.endTime > startTime;
|
||||
});
|
||||
}
|
||||
|
||||
function initReservationForm() {
|
||||
const form = document.getElementById('reservationForm');
|
||||
if (!form) return;
|
||||
@@ -52,18 +74,16 @@ function initDatePicker() {
|
||||
function initTimeSlots() {
|
||||
const select = document.getElementById('resStartTime');
|
||||
if (!select) return;
|
||||
|
||||
TIME_SLOTS.forEach(time => {
|
||||
const option = document.createElement('option');
|
||||
option.value = time;
|
||||
option.textContent = time;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
const currentMins = now.getMinutes();
|
||||
const nextSlot = TIME_SLOTS.find(t => {
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
const currentMins = now.getMinutes();
|
||||
const nextSlot = TIME_SLOTS.find(t => {
|
||||
const [h, m] = t.split(':').map(Number);
|
||||
return h > currentHour || (h === currentHour && m > currentMins);
|
||||
});
|
||||
@@ -83,14 +103,6 @@ function updatePricePreview() {
|
||||
document.getElementById('previewPrice').textContent = price + '€';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère la soumission du formulaire
|
||||
*
|
||||
* CORRIGÉ : on n'empêche plus la réservation si la place
|
||||
* est "reserved" dans le localStorage. On laisse le serveur
|
||||
* vérifier les conflits d'horaire. On bloque uniquement si
|
||||
* une voiture est physiquement détectée (status "occupied").
|
||||
*/
|
||||
async function handleReservationSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -111,25 +123,29 @@ async function handleReservationSubmit(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CORRIGÉ : on bloque uniquement si une voiture est physiquement là
|
||||
// Une place "reserved" peut quand même être réservée à un autre horaire
|
||||
const spots = JSON.parse(localStorage.getItem('smart_parking_spots') || '[]');
|
||||
const spot = spots.find(s => s.id === spotId);
|
||||
|
||||
if (spot && spot.status === 'occupied') {
|
||||
Dashboard.showToast('Une voiture est déjà sur cette place', 'error');
|
||||
Dashboard.showToast('Une voiture est deja sur cette place', 'error');
|
||||
if (window.ParkingMap) window.ParkingMap.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculer l'heure de fin
|
||||
const endDate = new Date(date + 'T' + startTime);
|
||||
endDate.setMinutes(endDate.getMinutes() + duration);
|
||||
const endTime = endDate.toTimeString().slice(0, 5);
|
||||
|
||||
// Essayer de créer la réservation via l'API
|
||||
// Le serveur vérifiera les conflits d'horaire
|
||||
if (checkLocalConflict(spotId, date, startTime, endTime)) {
|
||||
Dashboard.showToast(
|
||||
'Cette place est deja reservee a cet horaire. Essayez un autre creneau ou une autre date.',
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('smart_parking_token');
|
||||
let apiSuccess = false;
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
@@ -149,12 +165,10 @@ async function handleReservationSubmit(e) {
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
// Le serveur a détecté un conflit ou une erreur
|
||||
Dashboard.showToast(data.message, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Succès via API
|
||||
currentReservation = {
|
||||
id: data.data.id,
|
||||
userId: user.id,
|
||||
@@ -167,28 +181,34 @@ async function handleReservationSubmit(e) {
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
apiSuccess = true;
|
||||
|
||||
} catch (_err) {
|
||||
// Mode offline : enregistrement local
|
||||
currentReservation = creerReservationLocale(
|
||||
user, spotId, spot, date, startTime, endTime, duration, vehicle
|
||||
);
|
||||
apiSuccess = false;
|
||||
}
|
||||
} else {
|
||||
// Pas de token : mode offline
|
||||
currentReservation = creerReservationLocale(
|
||||
user, spotId, spot, date, startTime, endTime, duration, vehicle
|
||||
);
|
||||
}
|
||||
|
||||
// Sauvegarder dans le localStorage
|
||||
if (!apiSuccess) {
|
||||
currentReservation = {
|
||||
id: Date.now(),
|
||||
userId: user.id,
|
||||
userName: user.name,
|
||||
spotId: spotId,
|
||||
spotNumber: spot ? spot.number : spotId,
|
||||
date, startTime, endTime, duration,
|
||||
vehicle: vehicle.toUpperCase(),
|
||||
price: PRICING[duration],
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]');
|
||||
reservations.push(currentReservation);
|
||||
localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations));
|
||||
|
||||
// Mettre à jour la carte seulement si la réservation est pour aujourd'hui
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const now = new Date();
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const now = new Date();
|
||||
const resStart = new Date(date + 'T' + startTime);
|
||||
const diffMin = (resStart - now) / 60000;
|
||||
|
||||
@@ -197,40 +217,16 @@ async function handleReservationSubmit(e) {
|
||||
}
|
||||
|
||||
addToHistory(
|
||||
'Réservation',
|
||||
`Place ${currentReservation.spotNumber} réservée le ${date} de ${startTime} à ${endTime} — ${PRICING[duration]}€`
|
||||
'Reservation',
|
||||
'Place ' + currentReservation.spotNumber + ' reservee le ' + date + ' de ' + startTime + ' a ' + endTime + ' — ' + PRICING[duration] + '€'
|
||||
);
|
||||
|
||||
// Réinitialiser le formulaire
|
||||
document.getElementById('reservationForm').reset();
|
||||
initDatePicker();
|
||||
updatePricePreview();
|
||||
|
||||
showConfirmationModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une réservation en mode offline (localStorage)
|
||||
*/
|
||||
function creerReservationLocale(user, spotId, spot, date, startTime, endTime, duration, vehicle) {
|
||||
return {
|
||||
id: Date.now(),
|
||||
userId: user.id,
|
||||
userName: user.name,
|
||||
spotId: spotId,
|
||||
spotNumber: spot ? spot.number : spotId,
|
||||
date, startTime, endTime, duration,
|
||||
vehicle: vehicle.toUpperCase(),
|
||||
price: PRICING[duration],
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MODAL DE CONFIRMATION
|
||||
// ============================================
|
||||
|
||||
function initConfirmationModal() {
|
||||
document.getElementById('closeConfirmationModal')?.addEventListener('click', hideConfirmationModal);
|
||||
document.getElementById('closeConfirmationBtn')?.addEventListener('click', hideConfirmationModal);
|
||||
@@ -238,60 +234,44 @@ function initConfirmationModal() {
|
||||
|
||||
function showConfirmationModal() {
|
||||
if (!currentReservation) return;
|
||||
|
||||
const modal = document.getElementById('confirmationModal');
|
||||
|
||||
document.getElementById('paySpot').textContent = 'Place ' + currentReservation.spotNumber;
|
||||
document.getElementById('payDate').textContent = formatDate(currentReservation.date);
|
||||
document.getElementById('payTime').textContent = currentReservation.startTime + ' - ' + currentReservation.endTime;
|
||||
document.getElementById('payDuration').textContent = formatDuration(currentReservation.duration);
|
||||
document.getElementById('payTotal').textContent = currentReservation.price + '€';
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hideConfirmationModal() {
|
||||
document.getElementById('confirmationModal').classList.add('hidden');
|
||||
|
||||
if (window.Dashboard) {
|
||||
Dashboard.navigateToPage('my-reservations');
|
||||
document.querySelector('[data-page="my-reservations"]')?.classList.add('active');
|
||||
document.querySelector('[data-page="reservation"]')?.classList.remove('active');
|
||||
}
|
||||
|
||||
if (window.Dashboard) Dashboard.refreshStats();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// UTILITAIRES
|
||||
// ============================================
|
||||
|
||||
function addToHistory(action, details) {
|
||||
let history = JSON.parse(localStorage.getItem('smart_parking_history') || '[]');
|
||||
history.unshift({
|
||||
id: Date.now(),
|
||||
action,
|
||||
details,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
history.unshift({ id: Date.now(), action, details, timestamp: new Date().toISOString() });
|
||||
if (history.length > 100) history = history.slice(0, 100);
|
||||
localStorage.setItem('smart_parking_history', JSON.stringify(history));
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleDateString('fr-FR', {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric'
|
||||
});
|
||||
return new Date(dateString).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
}
|
||||
|
||||
function formatDuration(minutes) {
|
||||
if (minutes >= 480) return 'Journée (8h)';
|
||||
if (minutes >= 480) return 'Journee (8h)';
|
||||
if (minutes >= 60) {
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
return m > 0 ? `${h}h ${m}min` : `${h}h`;
|
||||
return m > 0 ? h + 'h ' + m + 'min' : h + 'h';
|
||||
}
|
||||
return `${minutes} min`;
|
||||
return minutes + ' min';
|
||||
}
|
||||
|
||||
window.Reservation = { PRICING, TIME_SLOTS, formatDuration, addToHistory };
|
||||
Reference in New Issue
Block a user