DO DISPATCH

LE CENTRE DE CONTRÔLE DE VOS COURSES VTC

● Serveur : vérification…
Lundi 21 septembre 2026--:--
◉ TRANSMOVEAdministrateur
▣ Réservations aujourd'hui—Planning du jour
♟ Chauffeurs connectés—● En ligne / disponibles
▰ Courses en cours—● Temps réel
€ Chiffre d'affaires—Réservations enregistrées

Dernières réservations

ToutesÀ affecterConfirméesEn coursTerminéesAnnulées
N°Date / HeureClientDépart → DestinationVéhiculeStatutMontantSourceActions

Détail de la réservation

Sélectionnez une réservation pour afficher sa fiche opérationnelle complète.

Calendrier des réservations

Septembre 2026

LMMJVSD123456789101112131415161718192021222324252627282930
`);w.document.close();setTimeout(()=>w.print(),250)}catch(e){alert(e.message)}} async function openDriverCompliance(id){try{const d=await get(`/drivers/${id}`),docs=await get(`/drivers/${id}/documents`),vehicles=await get(`/drivers/${id}/vehicles`);const docRows=docs.length?docs.map(x=>`${esc(x.document_type)}${esc(x.document_number||'')}${esc(x.expires_at||'—')}${esc(x.status)}`).join(''):'Aucun document enregistré.';showModal(`

Données chauffeur — ${esc(d.display_name)}

Documents, conformité, véhicule et historique opérationnel.

Documents & conformité

${docRows}
DocumentN°ÉchéanceÉtatAction

Véhicules

${vehicles.length?vehicles.map(v=>`
${esc(v.registration)}${esc((v.brand||'')+' '+(v.model||''))} ${v.category?'— '+esc(v.category):''}
`).join(''):'

Aucun véhicule enregistré.

'}
`);el('docForm').onsubmit=async e=>{e.preventDefault();const b=Object.fromEntries(new FormData(e.target).entries());['document_number','file_name','issued_at','expires_at','notes'].forEach(k=>{if(!b[k])b[k]=null});await request(`/drivers/${id}/documents`,{method:'POST',body:JSON.stringify(b)});openDriverCompliance(id)}}catch(e){alert(e.message)}} async function deleteDriverDocument(driverId,docId){if(!confirm('Retirer ce document du dossier chauffeur ?'))return;await request(`/drivers/${driverId}/documents/${docId}`,{method:'DELETE'});openDriverCompliance(driverId)} function renderBilling(){const b=el('billingBody');if(!b)return;b.innerHTML=state.reservations.map(r=>`${esc(r.reference)}${esc(r.customer_name)}${sourceLabel(r.source)}${esc(r.ride_date)}${money(r.client_price_ttc)}${esc(r.payment_status||'À vérifier')}${esc(r.invoice_status||'À créer')}`).join('')||'Aucune réservation à facturer.'} async function renderFinance(){try{const f=await get('/finance/summary');[['fCa',f.ca_ttc],['fDrivers',f.driver_due_ttc],['fMargin',f.gross_margin_ttc],['fPaid',f.paid_ttc],['fOutstanding',f.outstanding_ttc]].forEach(([id,v])=>{if(el(id))el(id).textContent=money(v)})}catch(e){}} async function renderSettlements(){try{const rows=await get('/driver-settlements');if(el('settlementsBody'))el('settlementsBody').innerHTML=rows.map(x=>{const d=state.drivers.find(z=>z.id===x.driver_id);return `${esc(d?.name||('Chauffeur #'+x.driver_id))}${esc(x.period_start)} → ${esc(x.period_end)}${esc(x.scheduled_payment_date)}${money(x.total_due_ttc)}${esc(x.status)}`}).join('')||'Aucun règlement préparé.'}catch(e){}} function settlementForm(){showModal(`

Préparer un règlement chauffeur

`);el('settlementForm').onsubmit=async e=>{e.preventDefault();const b=Object.fromEntries(new FormData(e.target).entries());b.driver_id=Number(b.driver_id);await request('/driver-settlements',{method:'POST',body:JSON.stringify(b)});closeModal();await renderSettlements()}} async function createMissingInvoices(){let n=0;for(const r of state.reservations){if(r.invoice_status!=='emise'){try{const x=await request('/client-invoices',{method:'POST',body:JSON.stringify({reservation_id:r.id})});if(x.created)n++}catch(e){}}}alert(`${n} facture(s) créée(s).`);await refreshAll()} async function renderIntegration(){try{const x=await get('/integration/status');if(el('integrationToken')){el('integrationToken').textContent=x.token_configured?'Jeton sécurisé configuré':'Jeton à configurer';el('integrationToken').className=x.token_configured?'good':'warning'}if(el('integrationMode'))el('integrationMode').textContent=`Mode : ${x.mode}${x.public_url?' — '+x.public_url:''}` }catch(e){}} function renderPremiumDashboard(){ const rows=[...state.reservations].sort((a,b)=>String(b.ride_date||'').localeCompare(String(a.ride_date||''))).slice(0,7); const body=el('dashReservationsBody'); if(body)body.innerHTML=rows.map(r=>`${esc((r.reference||'').replace(/[^0-9]/g,'')||r.reference)}${esc(r.ride_date)} ${(r.pickup_time||'').slice(0,5)}${esc(r.customer_name)}${esc(r.pickup)} → ${esc(r.destination)}${esc(r.vehicle_category||r.vehicle_type||'—')}${esc(statusLabel(r.status))}${money(r.client_price_ttc)}${esc(sourceLabel(r.source))}`).join('')||'Aucune réservation.'; const ca=state.reservations.reduce((n,r)=>n+Number(r.client_price_ttc||0),0);if(el('dashCa'))el('dashCa').textContent=money(ca); const acts=(state.audit||[]).slice(0,6);if(el('dashActivity'))el('dashActivity').innerHTML=acts.length?acts.map(a=>`
●
${esc(a.action||'Activité')}${esc(a.details||'')}
`).join(''):'

En attente de nouvelles activités.

'; if(rows.length)renderDashDetail(rows[0].id); } function renderDashDetail(id){const r=state.reservations.find(x=>x.id===id),box=el('dashDetail');if(!r||!box)return;box.innerHTML=`

Détail de la réservation ${esc(r.reference)}

● ${esc(statusLabel(r.status))}
Client${esc(r.customer_name)}Téléphone${esc(r.customer_phone||'—')}E-mail${esc(r.customer_email||'—')}Date / Heure${esc(r.ride_date)} ${(r.pickup_time||'').slice(0,5)}Départ${esc(r.pickup)}Destination${esc(r.destination)}Passagers${esc(r.passengers||'—')}Bagages${esc(r.luggage||'—')}Tarif TTC${money(r.client_price_ttc)} (TVA transport 10 %)Source${esc(sourceLabel(r.source))}
`} async function refreshAll(){try{ await get('/health');el('apiStatus').textContent='API : connectée';el('apiStatus').className='status ok'; const [d,cs,rs,ds,p,a]=await Promise.all([get('/dashboard'),get('/customers'),get('/reservations'),get('/drivers'),get('/planning'),get('/audit')]); state={customers:cs,reservations:rs,drivers:ds,planning:p,audit:a}; [['mCustomers',d.customers],['mReservations',d.reservations],['mDrivers',d.drivers],['mUnassigned',d.unassigned],['mToday',d.today]].forEach(([id,v])=>{const n=el(id);if(n)n.textContent=v??0}); renderCustomers();renderReservations();renderDispatch();renderPlanning();renderDrivers();renderAudit();renderBilling();renderFinance();renderSettlements();renderIntegration();renderPremiumDashboard(); }catch(e){el('apiStatus').textContent='API : hors ligne';el('apiStatus').className='status bad';console.error(e)}} el('newCustomerBtn').onclick=customerForm;el('newReservationBtn').onclick=reservationForm;el('customerSearch').oninput=renderCustomers;el('customerSourceFilter').onchange=renderCustomers;el('reservationSearch').oninput=renderReservations;el('reservationStatusFilter').onchange=renderReservations; function updateHeroClock(){const n=new Date();const d=el('heroDate'),t=el('heroTime');if(d)d.textContent=n.toLocaleDateString('fr-FR',{weekday:'long',day:'2-digit',month:'long',year:'numeric'});if(t)t.textContent=n.toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit'});} updateHeroClock();setInterval(updateHeroClock,1000); refreshAll(); /* DO_DISPATCH_NAVIGATION_UNIVERSELLE_V3 */