diff --git a/css/style.css b/css/style.css index b2225ed..0130158 100644 --- a/css/style.css +++ b/css/style.css @@ -185,7 +185,7 @@ body { @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } + to { opacity: 1; transform: translateY(0); } } .page-title { @@ -354,21 +354,10 @@ select.form-control { box-shadow: var(--shadow-lg); } -.stat-card.free { - border-left: 4px solid var(--spot-free); -} - -.stat-card.occupied { - border-left: 4px solid var(--spot-occupied); -} - -.stat-card.reserved { - border-left: 4px solid var(--spot-reserved); -} - -.stat-card.total { - border-left: 4px solid var(--primary); -} +.stat-card.free { border-left: 4px solid var(--spot-free); } +.stat-card.occupied { border-left: 4px solid var(--spot-occupied); } +.stat-card.reserved { border-left: 4px solid var(--spot-reserved); } +.stat-card.total { border-left: 4px solid var(--primary); } .stat-icon { font-size: 2rem; @@ -392,10 +381,10 @@ select.form-control { line-height: 1; } -.stat-card.free .stat-value { color: var(--spot-free); } +.stat-card.free .stat-value { color: var(--spot-free); } .stat-card.occupied .stat-value { color: var(--spot-occupied); } .stat-card.reserved .stat-value { color: var(--spot-reserved); } -.stat-card.total .stat-value { color: var(--primary); } +.stat-card.total .stat-value { color: var(--primary); } .stat-label { font-size: 0.85rem; @@ -448,32 +437,14 @@ select.form-control { background: var(--bg-dark); } -.parking-spot:hover { - transform: scale(1.05); -} +.parking-spot:hover { transform: scale(1.05); } -.parking-spot.free { - border-color: var(--spot-free); - color: var(--spot-free); -} +.parking-spot.free { border-color: var(--spot-free); color: var(--spot-free); } +.parking-spot.occupied { border-color: var(--spot-occupied); color: var(--spot-occupied); } +.parking-spot.reserved { border-color: var(--spot-reserved); color: var(--spot-reserved); } -.parking-spot.occupied { - border-color: var(--spot-occupied); - color: var(--spot-occupied); -} - -.parking-spot.reserved { - border-color: var(--spot-reserved); - color: var(--spot-reserved); -} - -.parking-spot .spot-number { - font-size: 1.1rem; -} - -.parking-spot .spot-icon { - font-size: 1.3rem; -} +.parking-spot .spot-number { font-size: 1.1rem; } +.parking-spot .spot-icon { font-size: 1.3rem; } /* Legend */ .legend { @@ -496,14 +467,12 @@ select.form-control { border-radius: 4px; } -.legend-color.free { background: var(--spot-free); } +.legend-color.free { background: var(--spot-free); } .legend-color.occupied { background: var(--spot-occupied); } .legend-color.reserved { background: var(--spot-reserved); } /* Spot details */ -.spot-details { - min-height: 200px; -} +.spot-details { min-height: 200px; } .no-selection { color: var(--text-muted); @@ -524,16 +493,10 @@ select.form-control { border-bottom: 1px solid var(--border); } -.spot-info-label { - color: var(--text-secondary); - font-size: 0.9rem; -} +.spot-info-label { color: var(--text-secondary); font-size: 0.9rem; } +.spot-info-value { font-weight: 600; } -.spot-info-value { - font-weight: 600; -} - -.spot-status-free { color: var(--spot-free); } +.spot-status-free { color: var(--spot-free); } .spot-status-occupied { color: var(--spot-occupied); } .spot-status-reserved { color: var(--spot-reserved); } @@ -605,7 +568,7 @@ select.form-control { background: var(--bg-card); border-radius: var(--border-radius); width: 100%; - max-width: 500px; + max-width: 480px; max-height: 90vh; overflow-y: auto; border: 1px solid var(--border); @@ -619,9 +582,7 @@ select.form-control { border-bottom: 1px solid var(--border); } -.modal-header h3 { - margin: 0; -} +.modal-header h3 { margin: 0; } .modal-close { background: none; @@ -632,15 +593,11 @@ select.form-control { transition: var(--transition); } -.modal-close:hover { - color: var(--text-primary); -} +.modal-close:hover { color: var(--text-primary); } -.modal-body { - padding: 24px; -} +.modal-body { padding: 24px; } -/* Payment modal */ +/* Récapitulatif réservation */ .payment-summary { background: var(--bg-dark); border-radius: var(--border-radius-sm); @@ -668,34 +625,37 @@ select.form-control { font-size: 1.1rem; } -.qr-section { +/* ============================================ + MESSAGE DE CONFIRMATION (remplace QR code) + ============================================ */ +.confirmation-message { text-align: center; - margin: 20px 0; -} - -.qr-section p { - margin-bottom: 12px; - color: var(--text-secondary); -} - -#qrcode { - display: flex; - justify-content: center; - margin: 16px 0; -} - -#qrcode img { + padding: 24px 16px; + background: rgba(16, 185, 129, 0.08); + border: 1px solid rgba(16, 185, 129, 0.3); border-radius: var(--border-radius-sm); + margin-bottom: 20px; } -.qr-info { - font-size: 0.85rem; +.confirmation-icon { + font-size: 3rem; + margin-bottom: 12px; } -.payment-actions { - display: flex; - flex-direction: column; - gap: 12px; +.confirmation-message h4 { + font-size: 1.1rem; + color: var(--success); + margin-bottom: 8px; +} + +.confirmation-message p { + color: var(--text-secondary); + font-size: 0.95rem; + line-height: 1.6; +} + +.confirmation-message strong { + color: var(--text-primary); } /* ============================================ @@ -735,6 +695,13 @@ select.form-control { gap: 4px; } +.reservation-actions { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 8px; +} + .reservation-price { font-size: 1.5rem; font-weight: 700; @@ -749,20 +716,9 @@ select.form-control { text-transform: uppercase; } -.status-active { - background: rgba(16, 185, 129, 0.2); - color: var(--success); -} - -.status-completed { - background: rgba(148, 163, 184, 0.2); - color: var(--text-muted); -} - -.status-cancelled { - background: rgba(239, 68, 68, 0.2); - color: var(--danger); -} +.status-active { background: rgba(16, 185, 129, 0.2); color: var(--success); } +.status-completed { background: rgba(148, 163, 184, 0.2); color: var(--text-muted); } +.status-cancelled { background: rgba(239, 68, 68, 0.2); color: var(--danger); } .empty-state { text-align: center; @@ -819,9 +775,7 @@ select.form-control { font-size: 2.5rem; } -.profile-header h3 { - margin-bottom: 8px; -} +.profile-header h3 { margin-bottom: 8px; } .role-badge { display: inline-block; @@ -924,14 +878,12 @@ select.form-control { cursor: pointer; } -.admin-place-item.free { background: rgba(16, 185, 129, 0.2); color: var(--spot-free); } -.admin-place-item.occupied { background: rgba(239, 68, 68, 0.2); color: var(--spot-occupied); } +.admin-place-item.free { background: rgba(16, 185, 129, 0.2); color: var(--spot-free); } +.admin-place-item.occupied { background: rgba(239, 68, 68, 0.2); color: var(--spot-occupied); } .admin-place-item.reserved { background: rgba(59, 130, 246, 0.2); color: var(--spot-reserved); } /* Tables */ -.table-container { - overflow-x: auto; -} +.table-container { overflow-x: auto; } .data-table { width: 100%; @@ -954,9 +906,7 @@ select.form-control { text-transform: uppercase; } -.data-table tr:hover td { - background: var(--bg-hover); -} +.data-table tr:hover td { background: var(--bg-hover); } /* Log container */ .log-container { @@ -975,9 +925,7 @@ select.form-control { font-size: 0.85rem; } -.log-item:last-child { - border-bottom: none; -} +.log-item:last-child { border-bottom: none; } .log-time { color: var(--text-muted); @@ -1009,116 +957,55 @@ select.form-control { } .toast.success { border-left-color: var(--success); } -.toast.error { border-left-color: var(--danger); } +.toast.error { border-left-color: var(--danger); } .toast.warning { border-left-color: var(--warning); } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } - to { transform: translateX(0); opacity: 1; } + to { transform: translateX(0); opacity: 1; } } /* ============================================ UTILITAIRES ============================================ */ -.hidden { - display: none !important; -} +.hidden { display: none !important; } -.admin-only { - display: none; -} - -.admin-only.visible { - display: flex; -} +.admin-only { display: none; } +.admin-only.visible { display: flex; } /* ============================================ RESPONSIVE ============================================ */ @media (max-width: 1024px) { - .stats-grid { - grid-template-columns: repeat(2, 1fr); - } - - .parking-section { - grid-template-columns: 1fr; - } - - .pricing-cards { - grid-template-columns: repeat(3, 1fr); - } - - .profile-container { - grid-template-columns: 1fr; - } - - .admin-page .admin-stats-grid { - grid-template-columns: repeat(2, 1fr); - } + .stats-grid { grid-template-columns: repeat(2, 1fr); } + .parking-section { grid-template-columns: 1fr; } + .pricing-cards { grid-template-columns: repeat(3, 1fr); } + .profile-container { grid-template-columns: 1fr; } + .admin-page .admin-stats-grid { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { - .header .container { - flex-wrap: wrap; - } - + .header .container { flex-wrap: wrap; } .nav { order: 3; width: 100%; justify-content: center; margin-top: 12px; } - - .nav-link { - padding: 8px 12px; - font-size: 0.8rem; - } - - .stats-grid { - grid-template-columns: 1fr; - } - - .form-row { - grid-template-columns: 1fr; - } - - .pricing-cards { - grid-template-columns: repeat(2, 1fr); - } - - .parking-map { - grid-template-columns: repeat(3, 1fr); - } - - .stats-cards { - grid-template-columns: 1fr; - } - - .admin-page .admin-stats-grid { - grid-template-columns: 1fr; - } - - .admin-places-list { - grid-template-columns: repeat(5, 1fr); - } + .nav-link { padding: 8px 12px; font-size: 0.8rem; } + .stats-grid { grid-template-columns: 1fr; } + .form-row { grid-template-columns: 1fr; } + .pricing-cards { grid-template-columns: repeat(2, 1fr); } + .parking-map { grid-template-columns: repeat(3, 1fr); } + .stats-cards { grid-template-columns: 1fr; } + .admin-page .admin-stats-grid { grid-template-columns: 1fr; } + .admin-places-list { grid-template-columns: repeat(5, 1fr); } + .reservation-card { flex-direction: column; align-items: flex-start; gap: 12px; } + .reservation-actions { align-items: flex-start; } } /* Scrollbar */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: var(--bg-dark); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb { - background: var(--border); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--secondary); -} +::-webkit-scrollbar { width: 8px; height: 8px; } +::-webkit-scrollbar-track { background: var(--bg-dark); border-radius: 4px; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } +::-webkit-scrollbar-thumb:hover { background: var(--bg-hover); } \ No newline at end of file diff --git a/js/dashboard.js b/js/dashboard.js index cf91dfe..3aaea1d 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -2,70 +2,48 @@ * ============================================ * DASHBOARD.JS - Gestion du dashboard * Smart Parking - BTS CIEL IR + * CORRIGÉ : cancelReservation utilisait spotNumber + * au lieu de spotId pour libérer la place * ============================================ */ -// Configuration const API_URL = 'http://localhost:3000/api'; -// État global let dashboardState = { - user: null, + user: null, currentPage: 'map' }; -// Initialisation document.addEventListener('DOMContentLoaded', () => { console.log('🚗 Initialisation du dashboard...'); - - // Vérifier l'authentification checkAuthentication(); - - // Initialiser la navigation initNavigation(); - - // Initialiser la déconnexion initLogout(); - - // Charger les données utilisateur loadUserData(); }); -/** - * Vérifie que l'utilisateur est authentifié - */ function checkAuthentication() { const token = localStorage.getItem('smart_parking_token'); - const user = localStorage.getItem('smart_parking_user'); - + const user = localStorage.getItem('smart_parking_user'); if (!token || !user) { - // Rediriger vers la page de connexion window.location.href = '../index.html'; return; } - dashboardState.user = JSON.parse(user); } -/** - * Initialise la navigation - */ function initNavigation() { const navLinks = document.querySelectorAll('.nav-link'); - navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const page = link.getAttribute('data-page'); navigateToPage(page); - - // Mettre à jour la classe active navLinks.forEach(l => l.classList.remove('active')); link.classList.add('active'); }); }); - - // Afficher le lien admin si l'utilisateur est admin + if (dashboardState.user && dashboardState.user.role === 'admin') { const adminLink = document.querySelector('.admin-only'); if (adminLink) { @@ -75,25 +53,18 @@ function initNavigation() { } } -/** - * Navigation entre les pages - */ function navigateToPage(page) { - // Cacher toutes les pages - const pages = document.querySelectorAll('.page'); - pages.forEach(p => { + document.querySelectorAll('.page').forEach(p => { p.classList.add('hidden'); p.classList.remove('active'); }); - - // Afficher la page demandée + const targetPage = document.getElementById(page); if (targetPage) { targetPage.classList.remove('hidden'); targetPage.classList.add('active'); dashboardState.currentPage = page; - - // Rafraîchir les données selon la page + if (page === 'map' && window.ParkingMap) { window.ParkingMap.refresh(); } else if (page === 'my-reservations') { @@ -104,9 +75,6 @@ function navigateToPage(page) { } } -/** - * Initialise la déconnexion - */ function initLogout() { const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { @@ -118,78 +86,59 @@ function initLogout() { } } -/** - * Charge les données utilisateur - */ function loadUserData() { const user = dashboardState.user; if (!user) return; - - // Mettre à jour l'affichage + document.getElementById('userName').textContent = user.name; document.getElementById('userRole').textContent = user.role === 'admin' ? 'Administrateur' : 'Client'; - - // Page profil - document.getElementById('profileName').textContent = user.name; - document.getElementById('profileNameInput').value = user.name; - document.getElementById('profileEmailInput').value = user.email; - document.getElementById('profilePhoneInput').value = user.phone || ''; - - // Badge rôle + + 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) { roleBadge.textContent = user.role === 'admin' ? 'Administrateur' : 'Client'; - roleBadge.className = 'role-badge ' + (user.role === 'admin' ? 'badge-admin' : 'badge-client'); + roleBadge.className = 'role-badge ' + (user.role === 'admin' ? 'badge-admin' : 'badge-client'); } - - // Charger les statistiques + loadUserStats(); } -/** - * Charge les statistiques utilisateur - */ function loadUserStats() { - const reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]'); + const reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]'); const userReservations = reservations.filter(r => r.userId === dashboardState.user.id); - - const totalReservations = userReservations.length; - const activeReservations = userReservations.filter(r => r.status === 'active').length; - const totalSpent = userReservations.reduce((sum, r) => sum + (r.price || 0), 0); - - document.getElementById('totalReservations').textContent = totalReservations; - document.getElementById('activeReservations').textContent = activeReservations; - document.getElementById('totalSpent').textContent = totalSpent + '€'; + + document.getElementById('totalReservations').textContent = userReservations.length; + document.getElementById('activeReservations').textContent = userReservations.filter(r => r.status === 'active').length; + document.getElementById('totalSpent').textContent = userReservations.reduce((s, r) => s + (r.price || 0), 0) + '€'; } -/** - * Charge les réservations de l'utilisateur - */ function loadMyReservations() { - const container = document.getElementById('myReservationsList'); + const container = document.getElementById('myReservationsList'); const emptyState = document.getElementById('noReservations'); - if (!container || !emptyState) return; - - const reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]'); + + const reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]'); const userReservations = reservations.filter(r => r.userId === dashboardState.user.id); - + if (userReservations.length === 0) { container.innerHTML = ''; emptyState.classList.remove('hidden'); return; } - + emptyState.classList.add('hidden'); - - container.innerHTML = userReservations.map(res => ` + container.innerHTML = userReservations.slice().reverse().map(res => `

Place ${res.spotNumber}

📅 ${res.date} 🕐 ${res.startTime} - ⏱️ ${res.duration} min + ⏱️ ${formatDurationLabel(res.duration)} 🚗 ${res.vehicle || 'N/A'}
@@ -207,94 +156,75 @@ function loadMyReservations() { } /** - * Annule une réservation + * 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') || '[]'); const reservation = reservations.find(r => r.id === reservationId); - + if (reservation) { - // Mettre à jour le statut reservation.status = 'cancelled'; localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations)); - - // Libérer la place + + // CORRIGÉ : spotId au lieu de spotNumber if (window.ParkingMap) { - window.ParkingMap.setSpotStatus(reservation.spotNumber, 'free'); + window.ParkingMap.setSpotStatus(reservation.spotId, 'free'); } - + showToast('Réservation annulée', 'success'); loadMyReservations(); loadUserStats(); } } -/** - * Met à jour le profil - */ +// Mise à jour du profil document.getElementById('profileForm')?.addEventListener('submit', (e) => { e.preventDefault(); - - const phone = document.getElementById('profilePhoneInput').value; + const phone = document.getElementById('profilePhoneInput').value; const newPassword = document.getElementById('profileNewPassword').value; - - // Mettre à jour l'utilisateur + let user = dashboardState.user; user.phone = phone; - - if (newPassword) { - user.password = newPassword; - } - - // Sauvegarder + if (newPassword) user.password = newPassword; + localStorage.setItem('smart_parking_user', JSON.stringify(user)); - - // Mettre à jour aussi dans la liste des utilisateurs + let users = JSON.parse(localStorage.getItem('smart_parking_users') || '[]'); - const userIndex = users.findIndex(u => u.id === user.id); - if (userIndex !== -1) { - users[userIndex] = user; + const idx = users.findIndex(u => u.id === user.id); + if (idx !== -1) { + users[idx] = user; localStorage.setItem('smart_parking_users', JSON.stringify(users)); } - + showToast('Profil mis à jour', 'success'); }); -/** - * Retourne le label du statut - */ function getStatusLabel(status) { - const labels = { - 'active': 'Active', - 'completed': 'Terminée', - 'cancelled': 'Annulée' - }; - return labels[status] || status; + return { active: 'Active', completed: 'Terminée', cancelled: 'Annulée' }[status] || status; +} + +function formatDurationLabel(minutes) { + if (minutes >= 480) return 'Journée'; + if (minutes >= 60) return Math.floor(minutes / 60) + 'h' + (minutes % 60 ? (minutes % 60) + 'min' : ''); + return minutes + ' min'; } -/** - * Affiche une notification toast - */ function showToast(message, type = 'info') { const container = document.getElementById('toastContainer'); - - const toast = document.createElement('div'); - toast.className = `toast ${type}`; + const toast = document.createElement('div'); + toast.className = `toast ${type}`; toast.textContent = message; - container.appendChild(toast); - - setTimeout(() => { - toast.remove(); - }, 3000); + setTimeout(() => toast.remove(), 3000); } -// Exporter les fonctions window.Dashboard = { navigateToPage, showToast, - getUser: () => dashboardState.user, + getUser: () => dashboardState.user, refreshStats: loadUserStats -}; +}; \ No newline at end of file diff --git a/js/reservation.js b/js/reservation.js index 680ae1d..cba13e1 100644 --- a/js/reservation.js +++ b/js/reservation.js @@ -2,6 +2,8 @@ * ============================================ * RESERVATION.JS - Système de réservation * Smart Parking - BTS CIEL IR + * MODIFIÉ : Suppression du QR code, remplacement + * par une confirmation simple avec badge * ============================================ */ @@ -33,7 +35,7 @@ document.addEventListener('DOMContentLoaded', () => { initDatePicker(); initTimeSlots(); initPricePreview(); - initPaymentModal(); + initConfirmationModal(); // MODIFIÉ : était initPaymentModal() }); /** @@ -42,7 +44,6 @@ document.addEventListener('DOMContentLoaded', () => { function initReservationForm() { const form = document.getElementById('reservationForm'); if (!form) return; - form.addEventListener('submit', handleReservationSubmit); } @@ -52,8 +53,6 @@ function initReservationForm() { function initDatePicker() { const dateInput = document.getElementById('resDate'); if (!dateInput) return; - - // Date minimum = aujourd'hui const today = new Date().toISOString().split('T')[0]; dateInput.min = today; dateInput.value = today; @@ -65,15 +64,15 @@ 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); }); - - // Sélectionner l'heure actuelle + 1h + + // Sélectionner le prochain créneau disponible const now = new Date(); const currentHour = now.getHours(); const currentMinutes = now.getMinutes(); @@ -81,10 +80,7 @@ function initTimeSlots() { const [h, m] = t.split(':').map(Number); return h > currentHour || (h === currentHour && m > currentMinutes); }); - - if (nextSlot) { - select.value = nextSlot; - } + if (nextSlot) select.value = nextSlot; } /** @@ -93,10 +89,7 @@ function initTimeSlots() { function initPricePreview() { const durationSelect = document.getElementById('resDuration'); if (!durationSelect) return; - durationSelect.addEventListener('change', updatePricePreview); - - // Prix initial updatePricePreview(); } @@ -106,246 +99,192 @@ function initPricePreview() { function updatePricePreview() { const duration = parseInt(document.getElementById('resDuration').value); const price = PRICING[duration] || 0; - document.getElementById('previewPrice').textContent = price + '€'; } /** * Gère la soumission du formulaire + * MODIFIÉ : la réservation est enregistrée ici directement, + * puis le modal de confirmation s'affiche. */ function handleReservationSubmit(e) { e.preventDefault(); - + const user = JSON.parse(localStorage.getItem('smart_parking_user') || 'null'); if (!user) { Dashboard.showToast('Veuillez vous connecter', 'error'); return; } - - const spotId = parseInt(document.getElementById('resSpot').value); - const date = document.getElementById('resDate').value; + + const spotId = parseInt(document.getElementById('resSpot').value); + const date = document.getElementById('resDate').value; const startTime = document.getElementById('resStartTime').value; - const duration = parseInt(document.getElementById('resDuration').value); - const vehicle = document.getElementById('resVehicle').value; - + const duration = parseInt(document.getElementById('resDuration').value); + const vehicle = document.getElementById('resVehicle').value; + if (!spotId || !date || !startTime || !vehicle) { Dashboard.showToast('Veuillez remplir tous les champs', 'error'); return; } - + // Vérifier que la place est toujours libre const spots = JSON.parse(localStorage.getItem('smart_parking_spots') || '[]'); - const spot = spots.find(s => s.id === spotId); - + const spot = spots.find(s => s.id === spotId); + if (!spot || spot.status !== 'free') { Dashboard.showToast('Cette place n\'est plus disponible', 'error'); - // Rafraîchir la carte - if (window.ParkingMap) { - window.ParkingMap.refresh(); - } + if (window.ParkingMap) window.ParkingMap.refresh(); return; } - + // Calculer l'heure de fin - const [hours, minutes] = startTime.split(':').map(Number); const endDate = new Date(date + 'T' + startTime); endDate.setMinutes(endDate.getMinutes() + duration); const endTime = endDate.toTimeString().slice(0, 5); - - // Créer la réservation + + // Construire l'objet réservation currentReservation = { - id: Date.now(), - userId: user.id, - userName: user.name, - spotId: spotId, - spotNumber: spot.number, - date: date, - startTime: startTime, - endTime: endTime, - duration: duration, - vehicle: vehicle.toUpperCase(), - price: PRICING[duration], - status: 'pending', - createdAt: new Date().toISOString() + id: Date.now(), + userId: user.id, + userName: user.name, + spotId: spotId, + spotNumber: spot.number, + date: date, + startTime: startTime, + endTime: endTime, + duration: duration, + vehicle: vehicle.toUpperCase(), + price: PRICING[duration], + status: 'active', + createdAt: new Date().toISOString() }; - - // Afficher le modal de paiement - showPaymentModal(); + + // ---- Enregistrement immédiat (plus de bouton "J'ai payé") ---- + + // Sauvegarder la réservation + let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]'); + reservations.push(currentReservation); + localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations)); + + // Mettre la place en "réservée" sur la carte + if (window.ParkingMap) { + window.ParkingMap.setSpotStatus(currentReservation.spotId, 'reserved'); + } + + // Ajouter à l'historique admin + addToHistory( + 'Réservation', + `Place ${currentReservation.spotNumber} réservée par ${currentReservation.userName} - ${currentReservation.price}€` + ); + + // Réinitialiser le formulaire + document.getElementById('reservationForm').reset(); + initDatePicker(); + updatePricePreview(); + + // Afficher le modal de confirmation + showConfirmationModal(); +} + +// ============================================ +// MODAL DE CONFIRMATION (remplace le QR code) +// ============================================ + +/** + * Initialise les événements du modal de confirmation + * REMPLACE : initPaymentModal() + */ +function initConfirmationModal() { + document.getElementById('closeConfirmationModal')?.addEventListener('click', hideConfirmationModal); + document.getElementById('closeConfirmationBtn')?.addEventListener('click', hideConfirmationModal); } /** - * Initialise le modal de paiement + * Affiche le modal de confirmation + * REMPLACE : showPaymentModal() + generateQRCode() */ -function initPaymentModal() { - // Fermer le modal - document.getElementById('closePaymentModal')?.addEventListener('click', hidePaymentModal); - document.getElementById('cancelPaymentBtn')?.addEventListener('click', hidePaymentModal); - - // Confirmer le paiement - document.getElementById('confirmPaymentBtn')?.addEventListener('click', confirmPayment); -} - -/** - * Affiche le modal de paiement - */ -function showPaymentModal() { +function showConfirmationModal() { if (!currentReservation) return; - - const modal = document.getElementById('paymentModal'); - + + const modal = document.getElementById('confirmationModal'); + // Remplir le récapitulatif - document.getElementById('paySpot').textContent = 'Place ' + currentReservation.spotNumber; - document.getElementById('payDate').textContent = formatDate(currentReservation.date); - document.getElementById('payTime').textContent = currentReservation.startTime + ' - ' + currentReservation.endTime; + 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 + '€'; - - // Générer le QR code - generateQRCode(); - + document.getElementById('payTotal').textContent = currentReservation.price + '€'; + // Afficher le modal modal.classList.remove('hidden'); } /** - * Cache le modal de paiement + * Cache le modal de confirmation + * REMPLACE : hidePaymentModal() */ -function hidePaymentModal() { - document.getElementById('paymentModal').classList.add('hidden'); -} +function hideConfirmationModal() { + document.getElementById('confirmationModal').classList.add('hidden'); -/** - * Génère le QR code - */ -function generateQRCode() { - const container = document.getElementById('qrcode'); - container.innerHTML = ''; - - // Générer un code de paiement unique - const paymentCode = 'PARK' + Date.now().toString().slice(-8); - document.getElementById('paymentCode').textContent = paymentCode; - - // Créer le QR code - const qrData = JSON.stringify({ - type: 'parking_payment', - reservationId: currentReservation.id, - amount: currentReservation.price, - code: paymentCode - }); - - // Utiliser QRCode.js si disponible, sinon afficher un faux QR - if (typeof QRCode !== 'undefined') { - new QRCode(container, { - text: qrData, - width: 200, - height: 200, - colorDark: '#000000', - colorLight: '#ffffff', - correctLevel: QRCode.CorrectLevel.M - }); - } else { - // Fallback - afficher un QR code simulé - container.innerHTML = ` -
-
📱
-
QR Code de paiement
-
- `; - } -} - -/** - * Confirme le paiement - */ -function confirmPayment() { - if (!currentReservation) return; - - // Mettre à jour le statut - currentReservation.status = 'active'; - - // Sauvegarder la réservation - let reservations = JSON.parse(localStorage.getItem('smart_parking_reservations') || '[]'); - reservations.push(currentReservation); - localStorage.setItem('smart_parking_reservations', JSON.stringify(reservations)); - - // Mettre à jour le statut de la place - if (window.ParkingMap) { - window.ParkingMap.setSpotStatus(currentReservation.spotId, 'reserved'); - } - - // Ajouter à l'historique admin - addToHistory('Réservation', `Place ${currentReservation.spotNumber} réservée par ${currentReservation.userName} - ${currentReservation.price}€`); - - // Fermer le modal - hidePaymentModal(); - - // Réinitialiser le formulaire - document.getElementById('reservationForm').reset(); - initDatePicker(); - updatePricePreview(); - - // Afficher confirmation - Dashboard.showToast('Réservation confirmée !', 'success'); - - // Rediriger vers mes réservations - setTimeout(() => { + // Rediriger vers "Mes réservations" après fermeture + if (window.Dashboard) { Dashboard.navigateToPage('my-reservations'); - document.querySelector('[data-page="my-reservations"]').classList.add('active'); - document.querySelector('[data-page="reservation"]').classList.remove('active'); - }, 1500); + document.querySelector('[data-page="my-reservations"]')?.classList.add('active'); + document.querySelector('[data-page="reservation"]')?.classList.remove('active'); + } + + // Rafraîchir les stats du profil + if (window.Dashboard) Dashboard.refreshStats(); } +// ============================================ +// FONCTIONS UTILITAIRES +// ============================================ + /** - * Ajoute à l'historique + * Ajoute une entrée à l'historique */ function addToHistory(action, details) { let history = JSON.parse(localStorage.getItem('smart_parking_history') || '[]'); history.unshift({ - id: Date.now(), - action: action, - details: details, + id: Date.now(), + action: action, + details: details, timestamp: new Date().toISOString() }); - - // Garder seulement les 100 dernières entrées - if (history.length > 100) { - history = history.slice(0, 100); - } - + if (history.length > 100) history = history.slice(0, 100); localStorage.setItem('smart_parking_history', JSON.stringify(history)); } /** - * Formate une date + * Formate une date DD/MM/YYYY */ function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('fr-FR', { - day: '2-digit', + day: '2-digit', month: '2-digit', - year: 'numeric' + year: 'numeric' }); } /** - * Formate une durée + * Formate une durée en minutes vers texte lisible */ function formatDuration(minutes) { - if (minutes >= 480) { - return 'Journée (8h)'; - } else if (minutes >= 60) { - const hours = Math.floor(minutes / 60); - const mins = minutes % 60; - return mins > 0 ? `${hours}h ${mins}min` : `${hours}h`; - } else { - return `${minutes} min`; + if (minutes >= 480) return 'Journée (8h)'; + if (minutes >= 60) { + const h = Math.floor(minutes / 60); + const m = minutes % 60; + return m > 0 ? `${h}h ${m}min` : `${h}h`; } + return `${minutes} min`; } -// Exporter les fonctions +// Exporter les fonctions publiques window.Reservation = { PRICING, TIME_SLOTS, formatDuration, addToHistory -}; +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c68b397 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Parking", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/pages/dashboard.html b/pages/dashboard.html index 6c5b3ef..264432f 100644 --- a/pages/dashboard.html +++ b/pages/dashboard.html @@ -6,8 +6,8 @@ Smart Parking - Dashboard + - @@ -206,64 +206,64 @@
- Prix total: + Prix total : 5€
+ - -