Journée d'essai vélos — Réservation
Azfalte · Mobilité vélo

Journée d'essai vélos

Réservez un créneau de 15 minutes pour tester un vélo à assistance électrique. 5 places disponibles par créneau.

Horaires10h00 – 14h00
Durée15 minutes / créneau
Places5 par créneau
Places disponibles Presque complet Complet

Retrouver ou annuler ma réservation

Entrez votre code agent pour retrouver votre créneau.

Espace organisateur

Vue d'ensemble des inscriptions — visible par toute personne accédant à cet outil.

${left <= 0 ? 'Complet' : left + ' place' + (left>1?'s':'') + ' restante' + (left>1?'s':'')}
`; if(left > 0){ btn.addEventListener('click', ()=>openModal(slot)); } grid.appendChild(btn); }); } function openModal(slot){ currentSlot = slot; document.getElementById('modalTime').textContent = "Créneau " + slot; document.getElementById('modalSpots').textContent = spotsLeft(slot) + " place(s) restante(s) sur " + CAPACITY; ['fNom','fPrenom','fEmail','fTel','fId'].forEach(id=>document.getElementById(id).value=''); const msg = document.getElementById('formMsg'); msg.className = 'msg'; msg.textContent=''; document.getElementById('overlay').classList.add('show'); } function closeModal(){ document.getElementById('overlay').classList.remove('show'); currentSlot = null; } function findExistingBooking(idCollab){ const norm = idCollab.trim().toLowerCase(); for(const slot of SLOTS){ const list = bookings[slot] || []; const found = list.find(b => b.id.trim().toLowerCase() === norm); if(found) return {slot, entry: found}; } return null; } async function submitBooking(){ const nom = document.getElementById('fNom').value.trim(); const prenom = document.getElementById('fPrenom').value.trim(); const email = document.getElementById('fEmail').value.trim(); const tel = document.getElementById('fTel').value.trim(); const id = document.getElementById('fId').value.trim(); const taille = document.getElementById('fTaille').value.trim(); const msg = document.getElementById('formMsg'); if(!nom || !prenom || !email || !tel || !id || !taille){ msg.className = 'msg err show'; msg.textContent = 'Merci de remplir tous les champs.'; return; } await loadBookings(); // refresh in case others booked meanwhile if(spotsLeft(currentSlot) <= 0){ msg.className = 'msg err show'; msg.textContent = 'Ce créneau vient de se remplir. Choisissez un autre créneau.'; renderGrid(); return; } const existing = findExistingBooking(id); if(existing){ msg.className = 'msg err show'; msg.textContent = 'Ce code agent est déjà inscrit au créneau ' + existing.slot + '.'; return; } if(!bookings[currentSlot]) bookings[currentSlot] = []; bookings[currentSlot].push({nom, prenom, email, tel, id, taille, ts: Date.now()}); try{ await saveBookings(); msg.className = 'msg ok show'; msg.textContent = 'Réservation confirmée pour ' + currentSlot + ' !'; renderGrid(); setTimeout(closeModal, 1100); }catch(e){ msg.className = 'msg err show'; msg.textContent = "Erreur lors de l'enregistrement. Réessayez."; } } async function lookupBooking(){ const id = document.getElementById('lookupId').value.trim(); const resultDiv = document.getElementById('lookupResult'); if(!id){ resultDiv.innerHTML = '

Entrez un code agent.

'; return; } await loadBookings(); const found = findExistingBooking(id); if(!found){ resultDiv.innerHTML = '

Aucune réservation trouvée pour ce code agent.

'; return; } resultDiv.innerHTML = `
Réservation trouvée : créneau ${found.slot} — ${found.entry.prenom} ${found.entry.nom}
`; document.getElementById('cancelBookingBtn').addEventListener('click', async ()=>{ bookings[found.slot] = bookings[found.slot].filter(b => b.id.trim().toLowerCase() !== id.trim().toLowerCase()); await saveBookings(); renderGrid(); resultDiv.innerHTML = '

Réservation annulée.

'; }); } function renderAdmin(){ const wrap = document.getElementById('adminTableWrap'); const rows = []; SLOTS.forEach(slot=>{ (bookings[slot]||[]).forEach(b=>{ rows.push({slot, ...b}); }); }); if(rows.length === 0){ wrap.innerHTML = '

Aucune inscription pour le moment.

'; return; } let html = ''; rows.forEach(r=>{ html += ``; }); html += '
CréneauNomPrénomEmailTéléphoneCode agentTaille
${r.slot} ${escapeHtml(r.nom)} ${escapeHtml(r.prenom)} ${escapeHtml(r.email)} ${escapeHtml(r.tel)} ${escapeHtml(r.id)} ${escapeHtml(r.taille || '')} cm Supprimer
'; wrap.innerHTML = html; wrap.querySelectorAll('.del-link').forEach(link=>{ link.addEventListener('click', async ()=>{ const slot = link.dataset.slot, id = link.dataset.id; bookings[slot] = (bookings[slot]||[]).filter(b => b.id !== id); await saveBookings(); renderGrid(); renderAdmin(); }); }); } function escapeHtml(str){ const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function exportCsv(){ let csv = 'Creneau;Nom;Prenom;Email;Telephone;Code agent;Taille (cm)\n'; SLOTS.forEach(slot=>{ (bookings[slot]||[]).forEach(b=>{ csv += [slot,b.nom,b.prenom,b.email,b.tel,b.id,b.taille||''].join(';') + '\n'; }); }); const blob = new Blob([csv], {type:'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'reservations-essai-velos.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } async function resetAll(){ if(!confirm('Supprimer toutes les réservations ? Cette action est irréversible.')) return; bookings = {}; await saveBookings(); renderGrid(); renderAdmin(); } document.getElementById('cancelModal').addEventListener('click', closeModal); document.getElementById('submitModal').addEventListener('click', submitBooking); document.getElementById('overlay').addEventListener('click', (e)=>{ if(e.target.id==='overlay') closeModal(); }); document.getElementById('lookupBtn').addEventListener('click', lookupBooking); document.getElementById('exportBtn').addEventListener('click', exportCsv); document.getElementById('resetBtn').addEventListener('click', resetAll); document.getElementById('toggleAdmin').addEventListener('click', ()=>{ const admin = document.getElementById('admin'); admin.classList.toggle('show'); if(admin.classList.contains('show')) renderAdmin(); }); (async function init(){ await loadBookings(); renderGrid(); })();