| window.onerror = function(msg, url, line) { |
| console.error('ERREUR JS :', msg, line); |
| }; |
|
|
| const state = { |
| isReturnTrip: false, |
| profile: { firstName: 'Marie', lastName: 'Dupont', phone: '06 12 34 56 78', email: 'marie@email.com', address: '' }, |
| customerType: 'particulier', |
| billingAddress: '', |
| company: { |
| name: '', |
| siret: '', |
| vat: '', |
| billingEmail: '' |
| }, |
| drivers: [ |
| { id: 'karim', name: 'Karim', photo: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face', status: 'Disponible', vehicle: 'Mercedes Classe E', rate: 2, rides: 18, favorite: true, available: true, phone: '+33612345678' }, |
| { id: 'sofiane', name: 'Sofiane', photo: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=100&h=100&fit=crop&crop=face', status: 'Disponible', vehicle: 'Tesla Model 3', rate: 2.2, rides: 11, favorite: false, available: true, phone: '+33623456789' }, |
| { id: 'nadia', name: 'Nadia', photo: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop&crop=face', status: 'Occupé', vehicle: 'Peugeot 508', rate: 1.9, rides: 7, favorite: false, available: false, phone: '+33634567890' }, |
| { id: 'rachid', name: 'Rachid', photo: 'https://images.unsplash.com/photo-1531123897727-8f129e1688ce?w=100&h=100&fit=crop&crop=face', status: 'Hors ligne', vehicle: 'Toyota Corolla', rate: 2, rides: 5, favorite: false, available: false, phone: '+33645678901' } |
| ], |
| favoriteAddresses: [ |
| { id:'home', label:'Maison', tag:'maison', icon:'home', address:'12 Rue de la Paix, Paris' }, |
| { id:'work', label:'Travail', tag:'travail', icon:'briefcase', address:'Bureau, Paris' }, |
| { id:'airport', label:'Aéroport', tag:'aéroport', icon:'plane', address:'Aéroport CDG, Terminal 2' }, |
| { id:'station', label:'Gare', tag:'gare', icon:'train', address:'Gare de Lyon, Paris' } |
| ], |
| rides: [ |
| { id: 1, date: "Aujourd'hui à 14:30", from: '12 Rue de la Paix', to: 'Aéroport CDG', driver: 'Karim', status: 'Confirmée', amount: '52 €', filter: 'today', vehicle: 'Mercedes Classe E', distance: '24 km', rate: 2, passengers: 1, luggage: 1, animal: false }, |
| { id: 2, date: 'Envoyée il y a 5 min', from: 'Domicile', to: 'Gare de Lyon', driver: 'Demande groupée', status: 'En attente', amount: null, filter: 'today', vehicle: '-', distance: '-', rate: null, passengers: 1, luggage: 1, animal: false }, |
| { id: 3, date: 'Demain à 09:00', from: 'Orly', to: 'La Défense', driver: 'Sofiane', status: 'Confirmée', amount: '45 €', filter: 'upcoming', vehicle: 'Tesla Model 3', distance: '22 km', rate: 2.2, passengers: 2, luggage: 2, animal: false }, |
| { id: 4, date: 'Hier à 18:15', from: 'Bureau', to: 'Domicile', driver: 'Karim', status: 'Terminée', amount: '28 €', filter: 'past', vehicle: 'Mercedes Classe E', distance: '14 km', rate: 2, passengers: 1, luggage: 0, animal: false }, |
| { id: 5, date: '12/01/2026', from: 'Domicile', to: 'Gare du Nord', driver: 'Nadia', status: 'Annulée', amount: '-', filter: 'cancelled', vehicle: 'Peugeot 508', distance: '-', rate: 1.9, passengers: 1, luggage: 1, animal: false } |
| ], |
| relatives: [ |
| { firstName: 'Lucas', lastName: 'Dupont', phone: '06 23 45 67 89' } |
| ], |
| segment: 'fav', |
| when: 'now', |
| passengers: 1, |
| luggage: 1, |
| animal: false, |
| forOther: false, |
| currentRide: null |
| }; |
|
|
| const views = ['home', 'drivers', 'rides', 'profile']; |
|
|
| function showView(name) { |
| views.forEach(v => { |
| const el = document.getElementById('view-' + v); |
| if (el) { |
| el.classList.add('hidden'); |
| el.classList.remove('flex'); |
| el.classList.remove('block'); |
| } |
| }); |
| const target = document.getElementById('view-' + name); |
| if (target) { |
| target.classList.remove('hidden'); |
| if (target.tagName.toLowerCase() === 'main') { |
| target.classList.add('block'); |
| } else { |
| target.classList.add('block'); |
| } |
| target.scrollTop = 0; |
| } |
| document.querySelectorAll('.nav-btn').forEach(btn => { |
| if (btn.dataset.view === name) { |
| btn.classList.remove('text-white/40'); |
| btn.classList.add('text-emerald-400'); |
| } else { |
| btn.classList.remove('text-emerald-400'); |
| btn.classList.add('text-white/40'); |
| } |
| }); |
| } |
|
|
| function openModal(id) { |
| const el = document.getElementById(id); |
| if (!el) return; |
| el.classList.remove('hidden'); |
| void el.offsetWidth; |
| el.classList.add('active'); |
| lucide.createIcons(); |
| } |
|
|
| function closeModal(id) { |
| const el = document.getElementById(id); |
| if (!el) return; |
| el.classList.remove('active'); |
| setTimeout(() => el.classList.add('hidden'), 300); |
| } |
|
|
| function renderDrivers() { |
| const container = document.getElementById('drivers-list'); |
| if (!container) return; |
| if (state.drivers.length === 0) { |
| container.innerHTML = '<p class="text-sm text-white/45 text-center py-8">Aucun chauffeur dans votre réseau.</p>'; |
| lucide.createIcons(); |
| return; |
| } |
| container.innerHTML = state.drivers.map(d => { |
| const statusColor = d.status === 'Disponible' ? 'text-emerald-400' : d.status === 'Occupé' ? 'text-amber-400' : 'text-white/40'; |
| return ` |
| <div class="bg-[#151821] border border-white/10 rounded-2xl p-4 shadow-sm flex items-center gap-4"> |
| <div class="relative shrink-0"> |
| <img src="${d.photo}" class="w-12 h-12 rounded-full object-cover opacity-90 border border-white/10"> |
| <span class="absolute bottom-0 right-0 w-3 h-3 ${d.status === 'Disponible' ? 'bg-emerald-400' : d.status === 'Occupé' ? 'bg-amber-400' : 'bg-gray-500'} rounded-full border-[2.5px] border-[#151821]"></span> |
| </div> |
| <div class="flex-1 min-w-0"> |
| <div class="flex items-center gap-2"> |
| <p class="text-sm font-semibold text-white">${d.name}</p> |
| <button onclick="setFavoriteDriver('${d.id}')" class="press"> |
| <i data-lucide="star" class="w-4 h-4 ${d.favorite ? 'text-yellow-400 fill-yellow-400' : 'text-white/30'}"></i> |
| </button> |
| </div> |
| <p class="text-xs ${statusColor}">${d.status}</p> |
| <p class="text-xs text-white/45 mt-0.5">${d.vehicle} • ${d.rate.toFixed(1).replace('.0','')} €/km • ${d.rides} courses</p> |
| </div> |
| <div class="flex gap-2 items-center"> |
| ${d.phone ? `<a href="tel:${d.phone}" class="press h-8 w-8 rounded-full bg-[#1a1f2b] border border-white/10 flex items-center justify-center text-emerald-400"><i data-lucide="phone" class="w-4 h-4"></i></a>` : ''} |
| <button onclick="selectDriver('${d.id}')" class="press h-8 px-3 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-xs font-semibold text-emerald-400 hover:bg-emerald-500/20 transition-colors">Choisir</button> |
| <button onclick="removeDriver('${d.id}')" class="press h-8 w-8 rounded-full bg-[#1a1f2b] border border-white/10 flex items-center justify-center text-red-400"> |
| <i data-lucide="trash-2" class="w-4 h-4"></i> |
| </button> |
| </div> |
| </div> |
| `; |
| }).join(''); |
| lucide.createIcons(); |
| } |
|
|
| function setFavoriteDriver(id) { |
| state.drivers.forEach(d => d.favorite = (d.id === id)); |
| renderDrivers(); |
| renderSelectedDriver(); |
| renderEstimate(); |
| } |
|
|
| function removeDriver(id) { |
| const wasFav = state.drivers.find(d => d.id === id)?.favorite; |
| state.drivers = state.drivers.filter(d => d.id !== id); |
| if (wasFav && state.drivers.length > 0) { |
| state.drivers[0].favorite = true; |
| } |
| renderDrivers(); |
| renderSelectedDriver(); |
| renderEstimate(); |
| } |
|
|
| function selectDriver(id) { |
| setFavoriteDriver(id); |
| setSegment('fav'); |
| showView('home'); |
| } |
|
|
| function selectDriverFromModal(id) { |
| setFavoriteDriver(id); |
| setSegment('fav'); |
| renderSelectedDriver(); |
| renderEstimate(); |
| closeModal('modal-select-driver'); |
| closeModal('modal-available-drivers'); |
| } |
|
|
| function renderSelectedDriver() { |
| const block = document.getElementById('driver-block'); |
| if (!block) return; |
| if (state.segment === 'group') { |
| block.innerHTML = ` |
| <div class="flex items-center gap-3 pointer-events-none"> |
| <div class="w-8 h-8 rounded-full bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400"> |
| <i data-lucide="users" class="w-4 h-4"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-white">Tous mes chauffeurs</p> |
| <p class="text-[10px] text-emerald-400">Demande groupée</p> |
| </div> |
| </div> |
| <div class="flex items-center gap-2 pointer-events-none"> |
| <i data-lucide="chevron-right" class="w-4 h-4 text-white/30"></i> |
| </div> |
| `; |
| } else { |
| const fav = state.drivers.find(d => d.favorite) || state.drivers[0]; |
| if (!fav) { |
| block.innerHTML = '<p class="text-sm text-white/45">Aucun chauffeur favori</p>'; |
| lucide.createIcons(); |
| return; |
| } |
| block.innerHTML = ` |
| <div class="flex items-center gap-3 pointer-events-none"> |
| <div class="relative"> |
| <img src="${fav.photo}" class="w-8 h-8 rounded-full object-cover border border-white/10"> |
| <span class="absolute bottom-0 right-0 w-2.5 h-2.5 ${fav.status === 'Disponible' ? 'bg-emerald-400' : fav.status === 'Occupé' ? 'bg-amber-400' : 'bg-gray-500'} rounded-full border-2 border-[#0b0b0f]"></span> |
| </div> |
| <div> |
| <p class="text-sm font-medium text-white">${fav.name}</p> |
| <p class="text-[10px] ${fav.status === 'Disponible' ? 'text-emerald-400' : fav.status === 'Occupé' ? 'text-amber-400' : 'text-white/40'}">${fav.status}</p> |
| </div> |
| </div> |
| <div class="flex items-center gap-2 pointer-events-auto"> |
| ${fav.phone ? `<a href="tel:${fav.phone}" onclick="event.stopPropagation()" 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>` : ''} |
| <i data-lucide="chevron-right" class="w-4 h-4 text-white/30 pointer-events-none"></i> |
| </div> |
| `; |
| } |
| block.classList.add('cursor-pointer'); |
| block.onclick = openSelectDriverSheet; |
| lucide.createIcons(); |
| } |
|
|
| function renderEstimate() { |
| const dep = document.getElementById('input-depart')?.value?.trim(); |
| const arr = document.getElementById('input-arrivee')?.value?.trim(); |
| const block = document.getElementById('estimate-block'); |
| const priceEl = document.getElementById('estimate-price'); |
| const preview = document.getElementById('route-preview'); |
| if (!dep || !arr) { |
| block?.classList.add('hidden'); |
| preview?.classList.add('hidden'); |
| return; |
| } |
| block?.classList.remove('hidden'); |
| preview?.classList.remove('hidden'); |
| const distance = 12; |
| let price = 0; |
| if (state.segment === 'fav') { |
| const fav = state.drivers.find(d => d.favorite) || state.drivers[0]; |
| if (fav) price = distance * fav.rate; |
| } else { |
| const availableRates = state.drivers.filter(d => d.available).map(d => d.rate); |
| const minRate = availableRates.length > 0 ? Math.min(...availableRates) : 0; |
| price = distance * minRate; |
| } |
| if (priceEl) priceEl.textContent = price.toFixed(2).replace('.00','') + ' €'; |
| } |
|
|
| function setSegment(type) { |
| state.segment = type; |
| const favBtn = document.getElementById('seg-fav'); |
| const groupBtn = document.getElementById('seg-group'); |
| if (type === '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'; |
| } 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'; |
| } |
| renderSelectedDriver(); |
| renderEstimate(); |
| } |
|
|
| function addStop() { |
| const container = document.getElementById('extra-stops'); |
| const div = document.createElement('div'); |
| div.className = 'flex items-center gap-3 bg-[#0b0b0f] border border-white/10 rounded-xl px-3 py-3 animate-fade-in'; |
| div.innerHTML = ` |
| <div class="w-2 h-2 rounded-full bg-amber-400 shrink-0"></div> |
| <input type="text" placeholder="Adresse de l'arrêt" class="w-full bg-transparent text-sm text-white placeholder-white/30 focus:outline-none" /> |
| <button onclick="this.parentElement.remove()" class="text-white/30 hover:text-red-400 transition-colors press"><i data-lucide="x" class="w-4 h-4"></i></button> |
| `; |
| container.appendChild(div); |
| lucide.createIcons(); |
| } |
|
|
| function setWhen(type) { |
| state.when = type; |
| const btnNow = document.getElementById('btn-now'); |
| const btnLater = document.getElementById('btn-later'); |
| if (type === 'now') { |
| btnNow.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'; |
| btnLater.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 { |
| btnNow.className = 'press flex-1 py-2.5 rounded-xl border text-sm font-medium transition-all bg-transparent border-white/10 text-white/45'; |
| btnLater.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'; |
| openModal('modal-datetime'); |
| } |
| } |
|
|
| function confirmDateTime() { |
| closeModal('modal-datetime'); |
| } |
|
|
| function adjust(key, delta) { |
| state[key] = Math.max(0, state[key] + delta); |
| if (key === 'passengers' && state[key] < 1) state[key] = 1; |
| const el = document.getElementById('val-' + key); |
| if (el) el.textContent = state[key]; |
| } |
|
|
| function toggleAnimal() { |
| state.animal = !state.animal; |
| const btn = document.getElementById('btn-animal'); |
| if (state.animal) { |
| btn.textContent = 'Oui'; |
| btn.classList.remove('border-white/10','text-white/45'); |
| btn.classList.add('border-emerald-500/30','bg-emerald-500/10','text-emerald-400'); |
| } else { |
| btn.textContent = 'Non'; |
| btn.classList.add('border-white/10','text-white/45'); |
| btn.classList.remove('border-emerald-500/30','bg-emerald-500/10','text-emerald-400'); |
| } |
| } |
|
|
| 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.classList.remove('border-white/10','text-white/45'); |
| btn.classList.add('border-emerald-500/30','bg-emerald-500/10','text-emerald-400'); |
| fields.classList.remove('hidden'); |
| } else { |
| btn.textContent = 'Non'; |
| btn.classList.add('border-white/10','text-white/45'); |
| btn.classList.remove('border-emerald-500/30','bg-emerald-500/10','text-emerald-400'); |
| fields.classList.add('hidden'); |
| } |
| } |
|
|
| function sendRequest() { |
| state.isReturnTrip = false; |
| renderReturnBadge(); |
| openModal('modal-loading'); |
| const title = document.getElementById('loading-title'); |
| const sub = document.getElementById('loading-sub'); |
| title.textContent = 'Demande envoyée'; |
| if (state.segment === 'group') { |
| sub.textContent = 'Vos chauffeurs de confiance reçoivent la demande…'; |
| setTimeout(() => { sub.textContent = 'Karim consulte…'; }, 1800); |
| setTimeout(() => { sub.textContent = 'Sofiane consulte…'; }, 2800); |
| setTimeout(() => { sub.textContent = 'Sofiane confirme votre trajet'; title.textContent = 'Course confirmée'; }, 3800); |
| setTimeout(() => { closeModal('modal-loading'); openModal('modal-return'); }, 4800); |
| } else { |
| const fav = state.drivers.find(d => d.favorite) || { name: 'Karim' }; |
| sub.textContent = fav.name + ' consulte votre demande…'; |
| setTimeout(() => { sub.textContent = 'Votre chauffeur vérifie sa disponibilité'; }, 1800); |
| setTimeout(() => { sub.textContent = fav.name + ' confirme votre trajet'; title.textContent = 'Course confirmée'; }, 3200); |
| setTimeout(() => { closeModal('modal-loading'); openModal('modal-return'); }, 4200); |
| } |
| } |
|
|
| function renderReturnBadge() { |
| const badge = document.getElementById('return-badge'); |
| if (!badge) return; |
| if (state.isReturnTrip) { |
| badge.classList.remove('hidden'); |
| } else { |
| badge.classList.add('hidden'); |
| } |
| } |
|
|
| function handleReturn(yes) { |
| closeModal('modal-return'); |
| if (yes) { |
| const dep = document.getElementById('input-depart').value; |
| const arr = document.getElementById('input-arrivee').value; |
| document.getElementById('input-depart').value = arr; |
| document.getElementById('input-arrivee').value = dep; |
| document.getElementById('extra-stops').innerHTML = ''; |
| state.isReturnTrip = true; |
| renderReturnBadge(); |
| renderEstimate(); |
| window.scrollTo({top:0, behavior:'smooth'}); |
| } else { |
| document.getElementById('input-depart').value = ''; |
| document.getElementById('input-arrivee').value = ''; |
| document.getElementById('extra-stops').innerHTML = ''; |
| state.isReturnTrip = false; |
| renderReturnBadge(); |
| renderEstimate(); |
| } |
| } |
|
|
| function showManualCode() { |
| document.getElementById('manual-code-area').classList.remove('hidden'); |
| } |
|
|
| function addManualCode() { |
| const val = document.getElementById('manual-code').value; |
| if (val.trim()) { |
| closeModal('modal-qr'); |
| } |
| } |
|
|
| function renderRides(filter) { |
| const filters = ['today','upcoming','cancelled','past']; |
| filters.forEach(f => { |
| const btn = document.getElementById('filter-' + f); |
| if (!btn) return; |
| if (f === filter) { |
| btn.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'; |
| } else { |
| btn.className = 'press px-3 py-1.5 rounded-full text-xs font-medium bg-[#151821] text-white/60 border border-white/10'; |
| } |
| }); |
| const list = document.getElementById('rides-list'); |
| const filtered = state.rides.filter(r => r.filter === filter); |
| if (filtered.length === 0) { |
| list.innerHTML = '<p class="text-sm text-white/45 text-center py-8">Aucune course dans cette catégorie.</p>'; |
| return; |
| } |
| list.innerHTML = filtered.map(r => { |
| const statusColor = r.status === 'Confirmée' ? 'bg-emerald-500/10 text-emerald-400' : r.status === 'En attente' ? 'bg-amber-500/10 text-amber-400' : r.status === 'Annulée' ? 'bg-red-500/10 text-red-400' : 'bg-blue-500/10 text-blue-400'; |
| return ` |
| <div class="bg-[#151821] border border-white/10 rounded-2xl p-4"> |
| <div class="flex items-center justify-between mb-3"> |
| <span class="text-xs text-white/45">${r.date}</span> |
| <span class="px-2 py-0.5 rounded-full ${statusColor} text-[10px] font-medium">${r.status}</span> |
| </div> |
| <p class="text-sm text-white">${r.from} → ${r.to}</p> |
| <div class="flex items-center justify-between mt-3"> |
| <p class="text-xs text-white/45">Chauffeur : ${r.driver}</p> |
| ${r.amount ? `<p class="text-xs font-medium text-white">${r.amount}</p>` : ''} |
| </div> |
| <button onclick="openRideDetail(${r.id})" class="press mt-3 w-full py-2 rounded-xl bg-[#1a1f2b] border border-white/10 text-xs font-medium text-white/80">Détail</button> |
| </div> |
| `; |
| }).join(''); |
| } |
|
|
| function openRideDetail(id) { |
| const ride = state.rides.find(r => r.id === id); |
| if (!ride) return; |
| state.currentRide = ride; |
| const content = document.getElementById('ride-detail-content'); |
| const statusColor = ride.status === 'Confirmée' ? 'text-emerald-400' : ride.status === 'En attente' ? 'text-amber-400' : ride.status === 'Annulée' ? 'text-red-400' : 'text-blue-400'; |
| let docButton = ''; |
| if (ride.status === 'Annulée') { |
| docButton = '<p class="text-xs text-white/40 mt-2">Aucun document disponible pour une course annulée.</p>'; |
| } else if (ride.filter === 'today' || ride.filter === 'upcoming') { |
| docButton = `<button onclick="openDocument('bdc')" class="press mt-3 w-full py-3 rounded-xl bg-emerald-500 text-[#0b0b0f] text-sm font-semibold">Voir le bon de commande</button>`; |
| } else if (ride.filter === 'past') { |
| docButton = `<button onclick="openDocument('invoice')" class="press mt-3 w-full py-3 rounded-xl bg-emerald-500 text-[#0b0b0f] text-sm font-semibold">Voir la facture</button>`; |
| } |
| content.innerHTML = ` |
| <div class="space-y-3"> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Date / Heure</span> |
| <span class="text-sm text-white">${ride.date}</span> |
| </div> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Trajet</span> |
| <span class="text-sm text-white text-right">${ride.from} → ${ride.to}</span> |
| </div> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Chauffeur</span> |
| <span class="text-sm text-white">${ride.driver}</span> |
| </div> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Véhicule</span> |
| <span class="text-sm text-white">${ride.vehicle}</span> |
| </div> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Distance</span> |
| <span class="text-sm text-white">${ride.distance}</span> |
| </div> |
| ${ride.rate ? `<div class="flex justify-between items-center"><span class="text-xs text-white/45">Tarif/km</span><span class="text-sm text-white">${ride.rate} €/km</span></div>` : ''} |
| ${ride.amount && ride.amount !== '-' ? `<div class="flex justify-between items-center pt-2 border-t border-white/10"><span class="text-xs text-white/45">Montant</span><span class="text-sm font-semibold text-white">${ride.amount}</span></div>` : ''} |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Statut</span> |
| <span class="text-sm font-medium ${statusColor}">${ride.status}</span> |
| </div> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Passagers</span> |
| <span class="text-sm text-white">${ride.passengers}</span> |
| </div> |
| <div class="flex justify-between items-center"> |
| <span class="text-xs text-white/45">Bagages</span> |
| <span class="text-sm text-white">${ride.luggage}</span> |
| </div> |
| ${docButton} |
| </div> |
| `; |
| openModal('modal-ride-detail'); |
| } |
|
|
| function openDocument(type) { |
| const ride = state.currentRide; |
| if (!ride) return; |
| if (type === 'bdc') { |
| document.getElementById('bdc-date').textContent = new Date().toLocaleDateString('fr-FR'); |
| document.getElementById('bdc-datetime').textContent = ride.date; |
| document.getElementById('bdc-trajet').textContent = ride.from + ' → ' + ride.to; |
| document.getElementById('bdc-distance').textContent = 'Distance estimée : ' + (ride.distance || '12 km'); |
| document.getElementById('bdc-rate').textContent = 'Tarif kilométrique : ' + (ride.rate ? ride.rate + ' €/km' : 'sur devis'); |
| document.getElementById('bdc-price').textContent = 'Prix indicatif TTC : ' + (ride.amount || '24 €'); |
| openModal('modal-bdc'); |
| } else if (type === 'invoice') { |
| document.getElementById('invoice-date').textContent = new Date().toLocaleDateString('fr-FR'); |
| document.getElementById('invoice-prestation').textContent = ride.date; |
| document.getElementById('invoice-time').textContent = ride.date.split('à')[1]?.trim() || '14:30'; |
| openModal('modal-invoice'); |
| } |
| } |
|
|
| function renderProfile() { |
| const container = document.getElementById('relatives-list'); |
| if (!container) return; |
| container.innerHTML = state.relatives.map(r => ` |
| <div class="bg-[#1a1f2b] border border-white/10 rounded-xl p-3 flex items-center justify-between"> |
| <div> |
| <p class="text-sm font-medium text-white">${r.firstName} ${r.lastName}</p> |
| <p class="text-xs text-white/45">${r.phone}</p> |
| </div> |
| </div> |
| `).join(''); |
| } |
|
|
| function addRelative() { |
| const first = document.getElementById('rel-firstname').value.trim(); |
| const last = document.getElementById('rel-lastname').value.trim(); |
| const phone = document.getElementById('rel-phone').value.trim(); |
| if (!first || !last) return; |
| state.relatives.push({ firstName: first, lastName: last, phone: phone || '—' }); |
| document.getElementById('rel-firstname').value = ''; |
| document.getElementById('rel-lastname').value = ''; |
| document.getElementById('rel-phone').value = ''; |
| renderProfile(); |
| closeModal('modal-add-relative'); |
| } |
|
|
| 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)'; |
| } |
| } |
|
|
| function openSelectDriverSheet() { |
| const container = document.getElementById('select-driver-list'); |
| if (!container) return; |
| if (state.drivers.length === 0) { |
| container.innerHTML = '<p class="text-sm text-white/45 text-center py-4">Aucun chauffeur.</p>'; |
| } else { |
| container.innerHTML = state.drivers.map(d => { |
| const statusColor = d.status === 'Disponible' ? 'text-emerald-400' : d.status === 'Occupé' ? 'text-amber-400' : 'text-white/40'; |
| return ` |
| <div class="bg-[#1a1f2b] border border-white/10 rounded-xl p-3 flex items-center gap-3"> |
| <div class="relative shrink-0"> |
| <img src="${d.photo}" 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.status === 'Disponible' ? 'bg-emerald-400' : d.status === 'Occupé' ? 'bg-amber-400' : 'bg-gray-500'} rounded-full border-2 border-[#1a1f2b]"></span> |
| </div> |
| <div class="flex-1 min-w-0"> |
| <p class="text-sm font-medium text-white">${d.name}</p> |
| <p class="text-xs ${statusColor}">${d.status}</p> |
| <p class="text-xs text-white/45">${d.vehicle} • ${d.rate.toFixed(1).replace('.0','')} €/km</p> |
| </div> |
| <div class="flex gap-2 items-center"> |
| ${d.phone ? `<a href="tel:${d.phone}" class="press h-8 w-8 rounded-full bg-[#151821] border border-white/10 flex items-center justify-center text-emerald-400"><i data-lucide="phone" class="w-4 h-4"></i></a>` : ''} |
| <button onclick="selectDriverFromModal('${d.id}')" class="press h-8 px-3 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-xs font-semibold text-emerald-400">Choisir</button> |
| </div> |
| </div> |
| `; |
| }).join(''); |
| } |
| openModal('modal-select-driver'); |
| } |
|
|
| function openAvailableDriversSheet() { |
| const container = document.getElementById('available-drivers-list'); |
| if (!container) return; |
| const available = state.drivers.filter(d => d.available); |
| if (available.length === 0) { |
| container.innerHTML = '<p class="text-sm text-white/45 text-center py-4">Aucun chauffeur disponible.</p>'; |
| } else { |
| container.innerHTML = available.map(d => ` |
| <div class="bg-[#1a1f2b] border border-white/10 rounded-xl p-3 flex items-center gap-3"> |
| <div class="relative shrink-0"> |
| <img src="${d.photo}" 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 bg-emerald-400 rounded-full border-2 border-[#1a1f2b]"></span> |
| </div> |
| <div class="flex-1 min-w-0"> |
| <p class="text-sm font-medium text-white">${d.name}</p> |
| <p class="text-xs text-emerald-400">Disponible</p> |
| <p class="text-xs text-white/45">${d.vehicle}</p> |
| </div> |
| <div class="flex gap-2 items-center"> |
| ${d.phone ? `<a href="tel:${d.phone}" class="press h-8 w-8 rounded-full bg-[#151821] border border-white/10 flex items-center justify-center text-emerald-400"><i data-lucide="phone" class="w-4 h-4"></i></a>` : ''} |
| <button onclick="selectDriverFromModal('${d.id}')" class="press h-8 px-3 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-xs font-semibold text-emerald-400">Choisir</button> |
| </div> |
| </div> |
| `).join(''); |
| } |
| openModal('modal-available-drivers'); |
| } |
|
|
| function renderFavoriteAddressPills() { |
| const departContainer = document.getElementById('favorite-depart-pills'); |
| const arriveeContainer = document.getElementById('favorite-arrivee-pills'); |
| if (!departContainer || !arriveeContainer) return; |
| const pills = (target) => state.favoriteAddresses.map(a => ` |
| <button onclick="setFavoriteAddress('${target}', '${a.id}')" class="press shrink-0 bg-[#151821] border border-white/10 rounded-full px-3 py-1.5 text-xs text-white/55 flex items-center gap-1.5 hover:border-emerald-500/30 hover:text-emerald-400 transition-colors"> |
| <i data-lucide="${a.icon || 'map-pin'}" class="w-3 h-3"></i> |
| ${a.label} |
| </button> |
| `).join(''); |
| departContainer.innerHTML = pills('depart'); |
| arriveeContainer.innerHTML = pills('arrivee'); |
| lucide.createIcons(); |
| } |
|
|
| function setFavoriteAddress(target, id) { |
| const addr = state.favoriteAddresses.find(a => a.id === id); |
| if (!addr) return; |
| if (target === 'depart') { |
| const input = document.getElementById('input-depart'); |
| if (input) input.value = addr.address; |
| } else if (target === 'arrivee') { |
| const input = document.getElementById('input-arrivee'); |
| if (input) input.value = addr.address; |
| } |
| renderEstimate(); |
| } |
|
|
| function renderFavoriteAddressesProfile() { |
| const container = document.getElementById('profile-favorite-addresses'); |
| if (!container) return; |
| if (state.favoriteAddresses.length === 0) { |
| container.innerHTML = '<p class="text-xs text-white/45">Aucune adresse favorite.</p>'; |
| return; |
| } |
| container.innerHTML = state.favoriteAddresses.map(a => ` |
| <div onclick="openEditAddress('${a.id}')" class="cursor-pointer bg-[#1a1f2b] border border-white/10 rounded-xl p-3 flex items-center justify-between press"> |
| <div class="min-w-0"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="${a.icon || 'map-pin'}" class="w-4 h-4 text-white/45"></i> |
| <p class="text-sm font-medium text-white">${a.label}</p> |
| </div> |
| <p class="text-xs text-white/45 mt-0.5 truncate">${a.address}</p> |
| </div> |
| <span class="text-[10px] text-white/30 uppercase tracking-wider border border-white/10 px-2 py-0.5 rounded-full shrink-0">${a.tag}</span> |
| </div> |
| `).join(''); |
| lucide.createIcons(); |
| } |
|
|
| function addFavoriteAddress() { |
| const label = document.getElementById('addr-label').value.trim(); |
| const address = document.getElementById('addr-address').value.trim(); |
| const tag = document.getElementById('addr-tag').value.trim() || label.toLowerCase(); |
| const icon = document.getElementById('addr-icon')?.value || 'map-pin'; |
| if (!label || !address) return; |
| const id = 'addr_' + Date.now(); |
| state.favoriteAddresses.push({ id, label, tag, icon, address }); |
| document.getElementById('addr-label').value = ''; |
| document.getElementById('addr-address').value = ''; |
| document.getElementById('addr-tag').value = ''; |
| if (document.getElementById('addr-icon')) document.getElementById('addr-icon').value = 'map-pin'; |
| closeModal('modal-add-address'); |
| renderFavoriteAddressesProfile(); |
| renderFavoriteAddressPills(); |
| } |
|
|
| let editingAddressId = null; |
|
|
| function openEditAddress(id) { |
| const addr = state.favoriteAddresses.find(a => a.id === id); |
| if (!addr) return; |
| editingAddressId = id; |
| document.getElementById('edit-addr-id').value = id; |
| document.getElementById('edit-addr-label').value = addr.label; |
| document.getElementById('edit-addr-address').value = addr.address; |
| document.getElementById('edit-addr-tag').value = addr.tag; |
| const iconSelect = document.getElementById('edit-addr-icon'); |
| if (iconSelect) iconSelect.value = addr.icon || 'map-pin'; |
| openModal('modal-edit-address'); |
| } |
|
|
| function saveEditedAddress() { |
| if (!editingAddressId) return; |
| const addr = state.favoriteAddresses.find(a => a.id === editingAddressId); |
| if (!addr) return; |
| addr.label = document.getElementById('edit-addr-label').value.trim(); |
| addr.address = document.getElementById('edit-addr-address').value.trim(); |
| addr.tag = document.getElementById('edit-addr-tag').value.trim(); |
| const iconSelect = document.getElementById('edit-addr-icon'); |
| if (iconSelect) addr.icon = iconSelect.value; |
| editingAddressId = null; |
| closeModal('modal-edit-address'); |
| renderFavoriteAddressesProfile(); |
| renderFavoriteAddressPills(); |
| } |
|
|
| function deleteFavoriteAddress(id) { |
| if (!id && editingAddressId) id = editingAddressId; |
| if (!id) return; |
| state.favoriteAddresses = state.favoriteAddresses.filter(a => a.id !== id); |
| editingAddressId = null; |
| closeModal('modal-edit-address'); |
| renderFavoriteAddressesProfile(); |
| renderFavoriteAddressPills(); |
| } |
|
|
| 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'; |
| if (companyFields) 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'; |
| if (companyFields) companyFields.classList.remove('hidden'); |
| } |
| } |
|
|
| function saveProfile() { |
| const first = document.getElementById('profile-firstname').value.trim(); |
| const last = document.getElementById('profile-lastname').value.trim(); |
| const phone = document.getElementById('profile-phone').value.trim(); |
| const email = document.getElementById('profile-email').value.trim(); |
| const address = document.getElementById('profile-address').value.trim(); |
| if (!first || !last) return; |
| state.profile = { firstName: first, lastName: last, phone, email, address }; |
| state.billingAddress = address; |
| const nameDisplay = document.getElementById('profile-name-display'); |
| const contactDisplay = document.getElementById('profile-contact-display'); |
| const typeDisplay = document.getElementById('profile-type-display'); |
| if (nameDisplay) nameDisplay.textContent = first + ' ' + last; |
| if (contactDisplay) contactDisplay.textContent = (phone ? phone + ' • ' : '') + (email || ''); |
| if (typeDisplay) { |
| if (state.customerType === 'professionnel' && state.company.name) { |
| typeDisplay.textContent = 'Professionnel • ' + state.company.name; |
| } else { |
| typeDisplay.textContent = state.customerType === 'particulier' ? 'Particulier' : 'Professionnel'; |
| } |
| } |
| if (state.customerType === 'professionnel') { |
| state.company.name = document.getElementById('company-name').value.trim(); |
| state.company.siret = document.getElementById('company-siret').value.trim(); |
| state.company.vat = document.getElementById('company-vat').value.trim(); |
| state.company.billingEmail = document.getElementById('company-billing-email').value.trim(); |
| } |
| closeModal('modal-edit-profile'); |
| } |
|
|
| window.addEventListener('DOMContentLoaded', () => { |
| lucide.createIcons(); |
| showView('home'); |
| renderDrivers(); |
| renderSelectedDriver(); |
| renderRides('today'); |
| renderProfile(); |
| renderFavoriteAddressPills(); |
| renderFavoriteAddressesProfile(); |
| const today = new Date().toISOString().split('T')[0]; |
| const dp = document.getElementById('date-picker'); |
| if (dp) dp.value = today; |
| const tp = document.getElementById('time-picker'); |
| if (tp) tp.value = '14:30'; |
| const depInput = document.getElementById('input-depart'); |
| const arrInput = document.getElementById('input-arrivee'); |
| if (depInput) depInput.addEventListener('input', renderEstimate); |
| if (arrInput) arrInput.addEventListener('input', renderEstimate); |
| }); |