Spaces:
Running
Running
| // ========================== | |
| // DATA & STATE | |
| // ========================== | |
| const state = { | |
| view: 'home', | |
| mode: 'course', | |
| segment: 'fav', | |
| when: 'now', | |
| duration: 120, | |
| passengers: 1, | |
| luggage: 1, | |
| animal: false, | |
| forOther: false, | |
| customWelcome: false, | |
| need: 'none', | |
| customerType: 'particulier', | |
| selectedDriver: { id: 1, name: 'Karim', available: true, img: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face' }, | |
| profile: { firstname: 'Marie', lastname: 'Dupont', phone: '06 12 34 56 78', email: 'marie@email.com', type: 'particulier' } | |
| }; | |
| const drivers = [ | |
| { id: 1, name: 'Karim B.', available: true, img: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face', rating: 4.9, trips: 124 }, | |
| { id: 2, name: 'Sophie L.', available: true, img: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face', rating: 4.8, trips: 89 }, | |
| { id: 3, name: 'Thomas R.', available: false, img: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face', rating: 4.9, trips: 210 }, | |
| { id: 4, name: 'Amira D.', available: true, img: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=100&h=100&fit=crop&crop=face', rating: 5.0, trips: 56 } | |
| ]; | |
| let rides = [ | |
| { id: 1, status: 'confirmed', depart: '12 Rue de la Paix, Paris', arrivee: 'Aéroport CDG, Terminal 2', driver: 'Karim', price: 52, date: 'Aujourd\'hui', time: '14:30', category: 'today' }, | |
| { id: 2, status: 'pending', depart: 'Domicile', arrivee: 'Gare de Lyon, Paris', driver: 'Demande groupée', price: 28, date: 'Aujourd\'hui', time: '16:00', category: 'today' }, | |
| { id: 3, status: 'past', depart: 'Maison', arrivee: 'Bureau', driver: 'Sophie', price: 18, date: 'Hier', time: '08:30', category: 'past' }, | |
| { id: 4, status: 'cancelled', depart: 'Restaurant', arrivee: 'Maison', driver: 'Thomas', price: 0, date: '12 jan.', time: '22:00', category: 'cancelled' } | |
| ]; | |
| let relatives = [ | |
| { id: 1, firstname: 'Jean', lastname: 'Dupont', phone: '06 98 76 54 32' } | |
| ]; | |
| let favoriteAddresses = [ | |
| { id: 1, label: 'Maison', address: '12 Rue de la Paix, 75002 Paris', tag: 'maison', icon: 'home' }, | |
| { id: 2, label: 'Bureau', address: '88 Avenue des Champs-Élysées, 75008 Paris', tag: 'travail', icon: 'briefcase' } | |
| ]; | |
| let extraStopsCount = 0; | |
| // ========================== | |
| // INIT | |
| // ========================== | |
| document.addEventListener('DOMContentLoaded', () => { | |
| lucide.createIcons(); | |
| renderDrivers(); | |
| renderRides('today'); | |
| renderRelatives(); | |
| renderFavoriteAddresses(); | |
| renderFavoritePills(); | |
| updateDriverBlock(); | |
| updateDisposalPrice(); | |
| // Simulate route preview when typing | |
| const departInput = document.getElementById('input-depart'); | |
| const arriveeInput = document.getElementById('input-arrivee'); | |
| [departInput, arriveeInput].forEach(input => { | |
| input.addEventListener('input', () => { | |
| const show = departInput.value.length > 3 && arriveeInput.value.length > 3; | |
| const routePreview = document.getElementById('route-preview'); | |
| const estimateBlock = document.getElementById('estimate-block'); | |
| if (show) { | |
| routePreview.classList.remove('hidden'); | |
| estimateBlock.classList.remove('hidden'); | |
| } else { | |
| routePreview.classList.add('hidden'); | |
| estimateBlock.classList.add('hidden'); | |
| } | |
| }); | |
| }); | |
| }); | |
| // ========================== | |
| // NAVIGATION | |
| // ========================== | |
| function showView(viewName) { | |
| document.querySelectorAll('.view-section').forEach(el => el.classList.add('hidden')); | |
| const target = document.getElementById(`view-${viewName}`); | |
| if (target) target.classList.remove('hidden'); | |
| document.querySelectorAll('.nav-btn').forEach(btn => { | |
| if (btn.dataset.view === viewName) { | |
| btn.classList.add('text-emerald-400'); | |
| btn.classList.remove('text-white/40'); | |
| } else { | |
| btn.classList.remove('text-emerald-400'); | |
| btn.classList.add('text-white/40'); | |
| } | |
| }); | |
| window.scrollTo(0, 0); | |
| lucide.createIcons(); | |
| } | |
| // ========================== | |
| // MODALS | |
| // ========================== | |
| function openModal(id) { | |
| const modal = document.getElementById(id); | |
| if (!modal) return; | |
| modal.classList.remove('hidden'); | |
| document.body.style.overflow = 'hidden'; | |
| lucide.createIcons(); | |
| } | |
| function closeModal(id) { | |
| const modal = document.getElementById(id); | |
| if (!modal) return; | |
| modal.classList.add('hidden'); | |
| document.body.style.overflow = ''; | |
| } | |
| // ========================== | |
| // HOME / BOOKING LOGIC | |
| // ========================== | |
| function setMode(mode) { | |
| state.mode = mode; | |
| const courseBtn = document.getElementById('seg-mode-course'); | |
| const disposalBtn = document.getElementById('seg-mode-disposal'); | |
| const courseInputs = document.getElementById('course-inputs'); | |
| const disposalFields = document.getElementById('disposal-fields'); | |
| if (mode === 'course') { | |
| courseBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| disposalBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all text-white/45 hover:text-white/70'; | |
| courseInputs.classList.remove('hidden'); | |
| disposalFields.classList.add('hidden'); | |
| } else { | |
| disposalBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| courseBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all text-white/45 hover:text-white/70'; | |
| courseInputs.classList.add('hidden'); | |
| disposalFields.classList.remove('hidden'); | |
| } | |
| } | |
| function setSegment(seg) { | |
| state.segment = seg; | |
| const favBtn = document.getElementById('seg-fav'); | |
| const groupBtn = document.getElementById('seg-group'); | |
| if (seg === 'fav') { | |
| favBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| groupBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all text-white/45 hover:text-white/70'; | |
| updateDriverBlock(); | |
| } else { | |
| groupBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| favBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all text-white/45 hover:text-white/70'; | |
| document.querySelector('#driver-block div div:nth-child(2) p:first-child').textContent = 'Tous mes chauffeurs'; | |
| document.querySelector('#driver-block div div:nth-child(2) p:nth-child(2)').textContent = '2 disponibles'; | |
| document.querySelector('#driver-block img').src = 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop&crop=face'; | |
| } | |
| } | |
| function setWhen(when) { | |
| state.when = when; | |
| const nowBtn = document.getElementById('btn-now'); | |
| const laterBtn = document.getElementById('btn-later'); | |
| if (when === 'now') { | |
| nowBtn.className = 'press flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all bg-emerald-500/10 border-emerald-500/30 text-emerald-400'; | |
| laterBtn.className = 'press flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all bg-transparent border-white/10 text-white/45'; | |
| } else { | |
| laterBtn.className = 'press flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all bg-emerald-500/10 border-emerald-500/30 text-emerald-400'; | |
| nowBtn.className = 'press flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all bg-transparent border-white/10 text-white/45'; | |
| openModal('modal-datetime'); | |
| } | |
| } | |
| function confirmDateTime() { | |
| const date = document.getElementById('date-picker').value; | |
| const time = document.getElementById('time-picker').value; | |
| if (date && time) { | |
| document.getElementById('btn-later').textContent = `${date.split('-').reverse().join('/')} ${time}`; | |
| closeModal('modal-datetime'); | |
| } | |
| } | |
| function addStop() { | |
| const container = document.getElementById('extra-stops'); | |
| const id = ++extraStopsCount; | |
| const stopHTML = ` | |
| <div id="stop-${id}" class="flex items-center gap-3 bg-[#0b0b0f] border border-white/10 rounded-xl px-3 py-3"> | |
| <div class="w-2 h-2 rounded-full bg-amber-400 shrink-0"></div> | |
| <input type="text" placeholder="Arrêt intermédiaire" class="w-full bg-transparent text-sm text-white placeholder-white/30 focus:outline-none" /> | |
| <button onclick="removeStop(${id})" class="press text-white/30 hover:text-red-400 transition-colors"> | |
| <i data-lucide="x" class="w-4 h-4"></i> | |
| </button> | |
| </div> | |
| `; | |
| container.insertAdjacentHTML('beforeend', stopHTML); | |
| lucide.createIcons(); | |
| } | |
| function removeStop(id) { | |
| const el = document.getElementById(`stop-${id}`); | |
| if (el) el.remove(); | |
| } | |
| function sendRequest() { | |
| openModal('modal-loading'); | |
| setTimeout(() => { | |
| closeModal('modal-loading'); | |
| openModal('modal-return'); | |
| // Add pending ride demo | |
| rides.unshift({ | |
| id: Date.now(), | |
| status: 'pending', | |
| depart: document.getElementById('input-depart').value || 'Adresse saisie', | |
| arrivee: document.getElementById('input-arrivee').value || 'Destination', | |
| driver: state.segment === 'fav' ? state.selectedDriver.name : 'Demande groupée', | |
| price: state.mode === 'course' ? 24 : 80, | |
| date: 'Aujourd\'hui', | |
| time: 'À venir', | |
| category: 'today' | |
| }); | |
| renderRides('today'); | |
| }, 2000); | |
| } | |
| function handleReturn(returnTrip) { | |
| closeModal('modal-return'); | |
| if (returnTrip) { | |
| document.getElementById('return-badge').classList.remove('hidden'); | |
| const temp = document.getElementById('input-depart').value; | |
| document.getElementById('input-depart').value = document.getElementById('input-arrivee').value; | |
| document.getElementById('input-arrivee').value = temp; | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| } else { | |
| document.getElementById('return-badge').classList.add('hidden'); | |
| } | |
| } | |
| // ========================== | |
| // DISPOSAL | |
| // ========================== | |
| function adjustDuration(minutes) { | |
| state.duration = Math.max(30, state.duration + minutes); | |
| const h = Math.floor(state.duration / 60); | |
| const m = state.duration % 60; | |
| document.getElementById('disposal-duration').textContent = `${h}h${m.toString().padStart(2, '0')}`; | |
| updateDisposalPrice(); | |
| } | |
| function updateDisposalPrice() { | |
| const hours = state.duration / 60; | |
| const price = Math.round(hours * 40); | |
| document.getElementById('disposal-price').textContent = `${price} €`; | |
| } | |
| function toggleChip(btn) { | |
| const isActive = btn.classList.contains('bg-emerald-500/10'); | |
| if (isActive) { | |
| btn.className = 'press px-3 py-1.5 rounded-full text-xs font-medium border border-white/10 text-white/55 hover:border-emerald-500/30 hover:text-emerald-400 transition-colors'; | |
| } else { | |
| btn.className = 'press px-3 py-1.5 rounded-full text-xs font-medium border border-emerald-500/20 bg-emerald-500/10 text-emerald-400 transition-colors'; | |
| } | |
| } | |
| function toggleCustomWelcome() { | |
| state.customWelcome = !state.customWelcome; | |
| const btn = document.getElementById('btn-custom-welcome'); | |
| const fields = document.getElementById('custom-welcome-fields'); | |
| if (state.customWelcome) { | |
| btn.textContent = 'Oui'; | |
| btn.className = 'press px-3 py-1.5 rounded-lg border border-emerald-500/30 text-xs font-medium text-emerald-400 bg-emerald-500/10 transition-all'; | |
| fields.classList.remove('hidden'); | |
| } else { | |
| btn.textContent = 'Non'; | |
| btn.className = 'press px-3 py-1.5 rounded-lg border border-white/10 text-xs font-medium text-white/45 transition-all'; | |
| fields.classList.add('hidden'); | |
| } | |
| } | |
| function setNeed(btn, need) { | |
| state.need = need; | |
| document.querySelectorAll('.need-chip').forEach(chip => { | |
| chip.className = 'press need-chip px-3 py-1.5 rounded-full text-xs font-medium border border-white/10 text-white/55 hover:border-emerald-500/30 hover:text-emerald-400 transition-all'; | |
| }); | |
| btn.className = 'press need-chip px-3 py-1.5 rounded-full text-xs font-medium border border-emerald-500/20 bg-emerald-500/10 text-emerald-400 transition-all'; | |
| document.getElementById('need-other-field').classList.toggle('hidden', need !== 'other'); | |
| } | |
| // ========================== | |
| // DETAILS MODAL | |
| // ========================== | |
| function adjust(type, delta) { | |
| if (type === 'passengers') { | |
| state.passengers = Math.max(1, state.passengers + delta); | |
| document.getElementById('val-passengers').textContent = state.passengers; | |
| } else if (type === 'luggage') { | |
| state.luggage = Math.max(0, state.luggage + delta); | |
| document.getElementById('val-luggage').textContent = state.luggage; | |
| } | |
| } | |
| function toggleAnimal() { | |
| state.animal = !state.animal; | |
| const btn = document.getElementById('btn-animal'); | |
| if (state.animal) { | |
| btn.textContent = 'Oui'; | |
| btn.className = 'press px-3 py-1.5 rounded-lg border border-emerald-500/30 text-xs font-medium text-emerald-400 bg-emerald-500/10 transition-all'; | |
| } else { | |
| btn.textContent = 'Non'; | |
| btn.className = 'press px-3 py-1.5 rounded-lg border border-white/10 text-xs font-medium text-white/45 transition-all'; | |
| } | |
| } | |
| function toggleForOther() { | |
| state.forOther = !state.forOther; | |
| const btn = document.getElementById('btn-for-other'); | |
| const fields = document.getElementById('fields-for-other'); | |
| if (state.forOther) { | |
| btn.textContent = 'Oui'; | |
| btn.className = 'press px-3 py-1.5 rounded-lg border border-emerald-500/30 text-xs font-medium text-emerald-400 bg-emerald-500/10 transition-all'; | |
| fields.classList.remove('hidden'); | |
| } else { | |
| btn.textContent = 'Non'; | |
| btn.className = 'press px-3 py-1.5 rounded-lg border border-white/10 text-xs font-medium text-white/45 transition-all'; | |
| fields.classList.add('hidden'); | |
| } | |
| } | |
| // ========================== | |
| // DRIVERS | |
| // ========================== | |
| function renderDrivers() { | |
| const container = document.getElementById('drivers-list'); | |
| container.innerHTML = drivers.map(d => ` | |
| <div class="flex items-center justify-between bg-[#151821] border border-white/10 rounded-xl px-3 py-3"> | |
| <div class="flex items-center gap-3"> | |
| <div class="relative"> | |
| <img src="${d.img}" class="w-10 h-10 rounded-full object-cover border border-white/10"> | |
| <span class="absolute bottom-0 right-0 w-2.5 h-2.5 ${d.available ? 'bg-emerald-400' : 'bg-gray-500'} rounded-full border-2 border-[#151821]"></span> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-white">${d.name}</p> | |
| <p class="text-[10px] ${d.available ? 'text-emerald-400' : 'text-white/40'}">${d.available ? 'Disponible' : 'Indisponible'}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <span class="text-xs text-white/45">${d.rating} ★</span> | |
| <i data-lucide="chevron-right" class="w-4 h-4 text-white/30"></i> | |
| </div> | |
| </div> | |
| `).join(''); | |
| lucide.createIcons(); | |
| } | |
| function openAvailableDriversSheet() { | |
| const list = document.getElementById('available-drivers-list'); | |
| const available = drivers.filter(d => d.available); | |
| list.innerHTML = available.map(d => ` | |
| <div class="flex items-center justify-between bg-[#0b0b0f] border border-white/10 rounded-xl px-3 py-3"> | |
| <div class="flex items-center gap-3"> | |
| <img src="${d.img}" class="w-10 h-10 rounded-full object-cover border border-white/10"> | |
| <div> | |
| <p class="text-sm font-medium text-white">${d.name}</p> | |
| <p class="text-[10px] text-emerald-400">Disponible maintenant</p> | |
| </div> | |
| </div> | |
| <button onclick="selectDriver(${d.id}); closeModal('modal-available-drivers')" class="press px-3 py-1.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 text-xs font-medium">Choisir</button> | |
| </div> | |
| `).join(''); | |
| openModal('modal-available-drivers'); | |
| lucide.createIcons(); | |
| } | |
| function openSelectDriverSheet() { | |
| const list = document.getElementById('select-driver-list'); | |
| list.innerHTML = drivers.map(d => ` | |
| <div onclick="selectDriver(${d.id}); closeModal('modal-select-driver')" class="cursor-pointer flex items-center justify-between bg-[#0b0b0f] border border-white/10 rounded-xl px-3 py-3"> | |
| <div class="flex items-center gap-3 pointer-events-none"> | |
| <div class="relative"> | |
| <img src="${d.img}" class="w-10 h-10 rounded-full object-cover border border-white/10"> | |
| <span class="absolute bottom-0 right-0 w-2.5 h-2.5 ${d.available ? 'bg-emerald-400' : 'bg-gray-500'} rounded-full border-2 border-[#0b0b0f]"></span> | |
| </div> | |
| <div> | |
| <p class="text-sm font-medium text-white">${d.name}</p> | |
| <p class="text-[10px] ${d.available ? 'text-emerald-400' : 'text-white/40'}">${d.available ? 'Disponible' : 'Indisponible'}</p> | |
| </div> | |
| </div> | |
| ${state.selectedDriver.id === d.id ? '<span class="text-emerald-400 text-xs font-medium">Sélectionné</span>' : ''} | |
| </div> | |
| `).join(''); | |
| openModal('modal-select-driver'); | |
| lucide.createIcons(); | |
| } | |
| function selectDriver(id) { | |
| const d = drivers.find(x => x.id === id); | |
| if (d) { | |
| state.selectedDriver = d; | |
| updateDriverBlock(); | |
| } | |
| } | |
| function updateDriverBlock() { | |
| const block = document.getElementById('driver-block'); | |
| block.querySelector('img').src = state.selectedDriver.img; | |
| block.querySelector('p.text-sm').textContent = state.selectedDriver.name; | |
| block.querySelector('p.text-\\[10px\\]').textContent = state.selectedDriver.available ? 'Disponible' : 'Indisponible'; | |
| block.querySelector('p.text-\\[10px\\]').className = `text-[10px] ${state.selectedDriver.available ? 'text-emerald-400' : 'text-white/40'}`; | |
| } | |
| // ========================== | |
| // RIDES | |
| // ========================== | |
| function renderRides(filter) { | |
| document.querySelectorAll('[id^="filter-"]').forEach(btn => { | |
| btn.className = 'press px-3 py-1.5 rounded-full text-xs font-medium bg-[#151821] text-white/60 border border-white/10'; | |
| }); | |
| const activeBtn = document.getElementById(`filter-${filter}`); | |
| if (activeBtn) activeBtn.className = 'press px-3 py-1.5 rounded-full text-xs font-medium bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| const filtered = rides.filter(r => r.category === filter); | |
| const container = document.getElementById('rides-list'); | |
| if (filtered.length === 0) { | |
| container.innerHTML = '<p class="text-sm text-white/45 text-center py-8">Aucune course</p>'; | |
| return; | |
| } | |
| container.innerHTML = filtered.map(r => ` | |
| <div onclick="openRideDetail(${r.id})" class="cursor-pointer bg-[#151821] border border-white/10 rounded-xl p-4 space-y-2"> | |
| <div class="flex items-center justify-between"> | |
| <span class="text-xs font-medium uppercase tracking-wider ${getStatusColor(r.status)}">${getStatusLabel(r.status)}</span> | |
| <span class="text-xs text-white/45">${r.date} • ${r.time}</span> | |
| </div> | |
| <div class="space-y-1"> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-1.5 h-1.5 rounded-full bg-emerald-400"></div> | |
| <p class="text-sm text-white/80 truncate">${r.depart}</p> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-1.5 h-1.5 rounded-full bg-red-400"></div> | |
| <p class="text-sm text-white/80 truncate">${r.arrivee}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center justify-between pt-2 border-t border-white/10"> | |
| <span class="text-xs text-white/45">${r.driver}</span> | |
| <span class="text-sm font-semibold text-white">${r.price > 0 ? r.price + ' €' : '-'}</span> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function getStatusColor(status) { | |
| if (status === 'confirmed') return 'text-emerald-400'; | |
| if (status === 'pending') return 'text-amber-400'; | |
| if (status === 'cancelled') return 'text-red-400'; | |
| return 'text-white/45'; | |
| } | |
| function getStatusLabel(status) { | |
| if (status === 'confirmed') return 'Confirmée'; | |
| if (status === 'pending') return 'En attente'; | |
| if (status === 'cancelled') return 'Annulée'; | |
| return 'Terminée'; | |
| } | |
| function openRideDetail(id) { | |
| const r = rides.find(x => x.id === id); | |
| if (!r) return; | |
| const content = document.getElementById('ride-detail-content'); | |
| content.innerHTML = ` | |
| <div class="space-y-3 text-white/65"> | |
| <div class="flex justify-between py-2 border-b border-white/10"> | |
| <span>Statut</span> | |
| <span class="${getStatusColor(r.status)}">${getStatusLabel(r.status)}</span> | |
| </div> | |
| <div class="flex justify-between py-2 border-b border-white/10"> | |
| <span>Départ</span> | |
| <span class="text-white text-right">${r.depart}</span> | |
| </div> | |
| <div class="flex justify-between py-2 border-b border-white/10"> | |
| <span>Arrivée</span> | |
| <span class="text-white text-right">${r.arrivee}</span> | |
| </div> | |
| <div class="flex justify-between py-2 border-b border-white/10"> | |
| <span>Chauffeur</span> | |
| <span class="text-white">${r.driver}</span> | |
| </div> | |
| <div class="flex justify-between py-2 border-b border-white/10"> | |
| <span>Date</span> | |
| <span class="text-white">${r.date} à ${r.time}</span> | |
| </div> | |
| <div class="flex justify-between py-2"> | |
| <span>Prix estimé</span> | |
| <span class="text-white font-medium">${r.price > 0 ? r.price + ' €' : '-'}</span> | |
| </div> | |
| </div> | |
| ${r.status === 'past' ? ` | |
| <div class="flex gap-3 mt-4"> | |
| <button onclick="openModal('modal-bdc')" class="press flex-1 h-11 rounded-xl bg-[#1a1f2b] border border-white/10 text-white text-xs font-medium">Bon de commande</button> | |
| <button onclick="openModal('modal-invoice')" class="press flex-1 h-11 rounded-xl bg-emerald-500 text-[#0b0b0f] text-xs font-semibold">Facture</button> | |
| </div> | |
| ` : ''} | |
| `; | |
| openModal('modal-ride-detail'); | |
| lucide.createIcons(); | |
| } | |
| // ========================== | |
| // PROFILE | |
| // ========================== | |
| function setCustomerType(type) { | |
| state.customerType = type; | |
| const partBtn = document.getElementById('seg-type-particulier'); | |
| const proBtn = document.getElementById('seg-type-professionnel'); | |
| const companyFields = document.getElementById('company-fields'); | |
| if (type === 'particulier') { | |
| partBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| proBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all text-white/45 hover:text-white/70'; | |
| companyFields.classList.add('hidden'); | |
| } else { | |
| proBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all bg-emerald-500/10 text-emerald-400 border border-emerald-500/20'; | |
| partBtn.className = 'press flex-1 py-2 rounded-lg text-xs font-medium transition-all text-white/45 hover:text-white/70'; | |
| companyFields.classList.remove('hidden'); | |
| } | |
| } | |
| function saveProfile() { | |
| const firstname = document.getElementById('profile-firstname').value; | |
| const lastname = document.getElementById('profile-lastname').value; | |
| const phone = document.getElementById('profile-phone').value; | |
| const email = document.getElementById('profile-email').value; | |
| state.profile = { ...state.profile, firstname, lastname, phone, email, type: state.customerType }; | |
| document.getElementById('profile-name-display').textContent = `${firstname} ${lastname}`; | |
| document.getElementById('profile-contact-display').textContent = `${phone} • ${email}`; | |
| document.getElementById('profile-type-display').textContent = state.customerType === 'particulier' ? 'Particulier' : 'Professionnel'; | |
| document.querySelector('h1').textContent = `Bonjour ${firstname}`; | |
| closeModal('modal-edit-profile'); | |
| } | |
| function renderRelatives() { | |
| const container = document.getElementById('relatives-list'); | |
| container.innerHTML = relatives.map(r => ` | |
| <div class="flex items-center justify-between bg-[#151821] border border-white/10 rounded-xl px-3 py-3"> | |
| <div> | |
| <p class="text-sm text-white font-medium">${r.firstname} ${r.lastname}</p> | |
| <p class="text-xs text-white/45">${r.phone}</p> | |
| </div> | |
| <a href="tel:${r.phone}" class="press h-7 w-7 rounded-full bg-[#1a1f2b] border border-white/10 flex items-center justify-center text-emerald-400"> | |
| <i data-lucide="phone" class="w-3.5 h-3.5"></i> | |
| </a> | |
| </div> | |
| `).join(''); | |
| lucide.createIcons(); | |
| } | |
| function addRelative() { | |
| const firstname = document.getElementById('rel-firstname').value; | |
| const lastname = document.getElementById('rel-lastname').value; | |
| const phone = document.getElementById('rel-phone').value; | |
| if (firstname && lastname && phone) { | |
| relatives.push({ id: Date.now(), firstname, lastname, phone }); | |
| renderRelatives(); | |
| closeModal('modal-add-relative'); | |
| document.getElementById('rel-firstname').value = ''; | |
| document.getElementById('rel-lastname').value = ''; | |
| document.getElementById('rel-phone').value = ''; | |
| } | |
| } | |
| function renderFavoriteAddresses() { | |
| const container = document.getElementById('profile-favorite-addresses'); | |
| const departPills = document.getElementById('favorite-depart-pills'); | |
| const arriveePills = document.getElementById('favorite-arrivee-pills'); | |
| const html = favoriteAddresses.map(a => ` | |
| <div onclick="openEditAddress(${a.id})" class="cursor-pointer flex items-center gap-3 bg-[#151821] border border-white/10 rounded-xl px-3 py-3"> | |
| <div class="w-8 h-8 rounded-lg bg-[#1a1f2b] border border-white/10 flex items-center justify-center shrink-0"> | |
| <i data-lucide="${a.icon}" class="w-4 h-4 text-white/60"></i> | |
| </div> | |
| <div class="flex-1 min-w-0"> | |
| <p class="text-sm text-white font-medium">${a.label}</p> | |
| <p class="text-xs text-white/45 truncate">${a.address}</p> | |
| </div> | |
| <i data-lucide="chevron-right" class="w-4 h-4 text-white/30 shrink-0"></i> | |
| </div> | |
| `).join(''); | |
| container.innerHTML = html; | |
| const pillHtml = favoriteAddresses.map(a => ` | |
| <button onclick="fillAddress('${a.address}')" class="press shrink-0 bg-[#1a1f2b] border border-white/10 rounded-full px-2.5 py-1 text-[10px] text-white/65 flex items-center gap-1"> | |
| <i data-lucide="${a.icon}" class="w-3 h-3"></i> | |
| ${a.label} | |
| </button> | |
| `).join(''); | |
| if (departPills) departPills.innerHTML = pillHtml; | |
| if (arriveePills) arriveePills.innerHTML = pillHtml; | |
| lucide.createIcons(); | |
| } | |
| function fillAddress(address) { | |
| const active = document.activeElement; | |
| if (active && active.id === 'input-arrivee') { | |
| active.value = address; | |
| } else { | |
| document.getElementById('input-depart').value = address; | |
| } | |
| } | |
| function addFavoriteAddress() { | |
| const label = document.getElementById('addr-label').value; | |
| const address = document.getElementById('addr-address').value; | |
| const tag = document.getElementById('addr-tag').value; | |
| const icon = document.getElementById('addr-icon').value; | |
| if (label && address) { | |
| favoriteAddresses.push({ id: Date.now(), label, address, tag, icon }); | |
| renderFavoriteAddresses(); | |
| closeModal('modal-add-address'); | |
| document.getElementById('addr-label').value = ''; | |
| document.getElementById('addr-address').value = ''; | |
| document.getElementById('addr-tag').value = ''; | |
| } | |
| } | |
| function openEditAddress(id) { | |
| const a = favoriteAddresses.find(x => x.id === id); | |
| if (!a) return; | |
| document.getElementById('edit-addr-id').value = a.id; | |
| document.getElementById('edit-addr-label').value = a.label; | |
| document.getElementById('edit-addr-address').value = a.address; | |
| document.getElementById('edit-addr-tag').value = a.tag || ''; | |
| document.getElementById('edit-addr-icon').value = a.icon; | |
| openModal('modal-edit-address'); | |
| } | |
| function saveEditedAddress() { | |
| const id = parseInt(document.getElementById('edit-addr-id').value); | |
| const a = favoriteAddresses.find(x => x.id === id); | |
| if (a) { | |
| a.label = document.getElementById('edit-addr-label').value; | |
| a.address = document.getElementById('edit-addr-address').value; | |
| a.tag = document.getElementById('edit-addr-tag').value; | |
| a.icon = document.getElementById('edit-addr-icon').value; | |
| renderFavoriteAddresses(); | |
| closeModal('modal-edit-address'); | |
| } | |
| } | |
| function deleteFavoriteAddress() { | |
| const id = parseInt(document.getElementById('edit-addr-id').value); | |
| favoriteAddresses = favoriteAddresses.filter(x => x.id !== id); | |
| renderFavoriteAddresses(); | |
| closeModal('modal-edit-address'); | |
| } | |
| function renderFavoritePills() { | |
| renderFavoriteAddresses(); | |
| } | |
| // ========================== | |
| // QR & MISC | |
| // ========================== | |
| function showManualCode() { | |
| document.getElementById('manual-code-area').classList.remove('hidden'); | |
| } | |
| function addManualCode() { | |
| const code = document.getElementById('manual-code').value; | |
| if (code) { | |
| closeModal('modal-qr'); | |
| // Demo: add a new driver | |
| drivers.push({ | |
| id: Date.now(), | |
| name: `Chauffeur ${code.slice(0,4)}`, | |
| available: true, | |
| img: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face', | |
| rating: 5.0, | |
| trips: 0 | |
| }); | |
| renderDrivers(); | |
| document.getElementById('manual-code').value = ''; | |
| document.getElementById('manual-code-area').classList.add('hidden'); | |
| } | |
| } | |
| function toggleFaq(btn) { | |
| const answer = btn.nextElementSibling; | |
| const icon = btn.querySelector('.faq-icon'); | |
| if (answer.classList.contains('hidden')) { | |
| answer.classList.remove('hidden'); | |
| icon.style.transform = 'rotate(180deg)'; | |
| } else { | |
| answer.classList.add('hidden'); | |
| icon.style.transform = 'rotate(0deg)'; | |
| } | |
| } |