diff --git a/garage.php b/garage.php
index 7a5b75e..3f578b7 100644
--- a/garage.php
+++ b/garage.php
@@ -217,15 +217,25 @@ require __DIR__ . '/header.php';
+
diff --git a/modules/garage/api.php b/modules/garage/api.php
index 312a07b..d519000 100644
--- a/modules/garage/api.php
+++ b/modules/garage/api.php
@@ -109,14 +109,31 @@ if ($action === 'vehicles') {
}
if ($method === 'POST') {
$photo = handleUpload('photo', $UPLOAD_DIR); $d = $_POST ?: gBody();
- $stmt = $pdo->prepare("INSERT INTO pf_vehicles (name,brand,model,year,license_plate,vin,fuel_type,color,purchase_date,purchase_price,current_km,photo,notes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
- $stmt->execute([$d['name']??'', $d['brand']??'', $d['model']??'', $d['year']??null, $d['license_plate']??null, $d['vin']??null, $d['fuel_type']??'Essence', $d['color']??null, $d['purchase_date']??null, $d['purchase_price']??null, $d['current_km']??0, $photo, $d['notes']??null]);
+ // Ajout de consumption dans la liste des champs et d'un '?' supplémentaire
+ $stmt = $pdo->prepare("INSERT INTO pf_vehicles (name,brand,model,year,license_plate,vin,fuel_type,consumption,color,purchase_date,purchase_price,current_km,photo,notes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
+ $stmt->execute([
+ $d['name']??'',
+ $d['brand']??'',
+ $d['model']??'',
+ $d['year']??null,
+ $d['license_plate']??null,
+ $d['vin']??null,
+ $d['fuel_type']??'Essence',
+ ($d['consumption'] !== '' && $d['consumption'] !== null) ? (float)$d['consumption'] : null, // Cast propre en float
+ $d['color']??null,
+ $d['purchase_date']??null,
+ $d['purchase_price']??null,
+ $d['current_km']??0,
+ $photo,
+ $d['notes']??null
+ ]);
gOk(['id' => $pdo->lastInsertId()]);
}
if ($method === 'PUT') {
$id = $_GET['id'] ?? null; if (!$id) gErr('ID manquant'); $d = gBody();
- $int_fields = ['year','current_km']; $float_fields = ['purchase_price'];
- $fields = ['name','brand','model','year','license_plate','vin','fuel_type','color','purchase_date','purchase_price','current_km','notes'];
+ $int_fields = ['year','current_km'];
+ $float_fields = ['purchase_price', 'consumption'];
+ $fields = ['name','brand','model','year','license_plate','vin','fuel_type','consumption','color','purchase_date','purchase_price','current_km','notes'];
$sets = array_map(fn($f) => "$f = ?", $fields);
$vals = array_map(function($f) use ($d, $int_fields, $float_fields) {
$v = $d[$f] ?? null;
diff --git a/modules/garage/assets/garage.js b/modules/garage/assets/garage.js
index 14a46bc..0b9fd86 100644
--- a/modules/garage/assets/garage.js
+++ b/modules/garage/assets/garage.js
@@ -1,784 +1,1550 @@
// GarageManager - SPA Frontend
-const API = '/modules/garage/api.php';
-const UPLOADS = '/modules/garage/api.php?action=photo&file=';
+const API = "/modules/garage/api.php";
+const UPLOADS = "/modules/garage/api.php?action=photo&file=";
-function $(s,ctx=document){return ctx.querySelector(s);}
-function $$(s,ctx=document){return [...ctx.querySelectorAll(s)];}
-function fmt(n){return n!=null?Number(n).toLocaleString('fr-FR'):'--';}
-function fmtPrice(n){return n!=null?Number(n).toFixed(2)+' €':'--';}
-function fmtDate(d){if(!d)return '--';return new Date(d).toLocaleDateString('fr-FR');}
-function formatBytes(n){
- n=Number(n)||0;
- if(n<1024)return n+' o';
- if(n<1048576)return (n/1024).toFixed(1)+' Ko';
- return (n/1048576).toFixed(1)+' Mo';
+function $(s, ctx = document) {
+ return ctx.querySelector(s);
}
-function ago(d){if(!d)return '';const diff=Math.floor((Date.now()-new Date(d))/86400000);if(diff===0)return "aujourd'hui";if(diff===1)return 'hier';return 'il y a '+diff+'j';}
-function escHtml(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML;}
-function toast(msg,type='success'){
- const c=document.getElementById('toasts');const t=document.createElement('div');
- t.className='toast '+type;t.textContent=msg;c.appendChild(t);setTimeout(()=>t.remove(),3000);
+function $$(s, ctx = document) {
+ return [...ctx.querySelectorAll(s)];
+}
+function fmt(n) {
+ return n != null ? Number(n).toLocaleString("fr-FR") : "--";
+}
+function fmtPrice(n) {
+ return n != null ? Number(n).toFixed(2) + " €" : "--";
+}
+function fmtDate(d) {
+ if (!d) return "--";
+ return new Date(d).toLocaleDateString("fr-FR");
+}
+function formatBytes(n) {
+ n = Number(n) || 0;
+ if (n < 1024) return n + " o";
+ if (n < 1048576) return (n / 1024).toFixed(1) + " Ko";
+ return (n / 1048576).toFixed(1) + " Mo";
+}
+function ago(d) {
+ if (!d) return "";
+ const diff = Math.floor((Date.now() - new Date(d)) / 86400000);
+ if (diff === 0) return "aujourd'hui";
+ if (diff === 1) return "hier";
+ return "il y a " + diff + "j";
+}
+function escHtml(s) {
+ const d = document.createElement("div");
+ d.textContent = String(s ?? "");
+ return d.innerHTML;
+}
+function toast(msg, type = "success") {
+ const c = document.getElementById("toasts");
+ const t = document.createElement("div");
+ t.className = "toast " + type;
+ t.textContent = msg;
+ c.appendChild(t);
+ setTimeout(() => t.remove(), 3000);
}
// ─── STATE ────────────────────────────────────────────────────────────────────
-let currentPage='dashboard';
-let currentVehicleId=null;
-let currentMaintenanceId=null;
+let currentPage = "dashboard";
+let currentVehicleId = null;
+let currentMaintenanceId = null;
// ─── NAV ──────────────────────────────────────────────────────────────────────
-function navigate(page,params={}){
- currentPage=page;
- $$('.page').forEach(p=>p.classList.remove('active'));
- $$('.nav-link').forEach(n=>n.classList.remove('active'));
- document.getElementById('page-'+page)?.classList.add('active');
- const navTarget=page==='maintenance'?'maintenances-all':page==='vehicle'?'vehicles':page;
- document.querySelector('.nav-link[data-page="'+navTarget+'"]')?.classList.add('active');
- if(page==='dashboard')loadDashboard();
- else if(page==='vehicles')loadVehicles();
- else if(page==='vehicle')loadVehicleDetail(params.id);
- else if(page==='maintenance')loadMaintenanceDetail(params.id);
- else if(page==='parts')loadParts();
- else if(page==='maintenances-all')loadAllMaintenances();
+function navigate(page, params = {}) {
+ currentPage = page;
+ $$(".page").forEach((p) => p.classList.remove("active"));
+ $$(".nav-link").forEach((n) => n.classList.remove("active"));
+ document.getElementById("page-" + page)?.classList.add("active");
+ const navTarget =
+ page === "maintenance"
+ ? "maintenances-all"
+ : page === "vehicle"
+ ? "vehicles"
+ : page;
+ document
+ .querySelector('.nav-link[data-page="' + navTarget + '"]')
+ ?.classList.add("active");
+ if (page === "dashboard") loadDashboard();
+ else if (page === "vehicles") loadVehicles();
+ else if (page === "vehicle") loadVehicleDetail(params.id);
+ else if (page === "maintenance") loadMaintenanceDetail(params.id);
+ else if (page === "parts") loadParts();
+ else if (page === "maintenances-all") loadAllMaintenances();
}
// ─── API ──────────────────────────────────────────────────────────────────────
-async function api(action,method='GET',data=null,extra=''){
- const opts={method,headers:{}};
- if(data&&!(data instanceof FormData)){opts.headers['Content-Type']='application/json';opts.body=JSON.stringify(data);}
- else if(data instanceof FormData){opts.body=data;}
- const r=await fetch(API+'?action='+action+extra,opts);
- const j=await r.json();
- if(!j.ok)throw new Error(j.error||'Erreur API');
+async function api(action, method = "GET", data = null, extra = "") {
+ const opts = { method, headers: {} };
+ if (data && !(data instanceof FormData)) {
+ opts.headers["Content-Type"] = "application/json";
+ opts.body = JSON.stringify(data);
+ } else if (data instanceof FormData) {
+ opts.body = data;
+ }
+ const r = await fetch(API + "?action=" + action + extra, opts);
+ const j = await r.json();
+ if (!j.ok) throw new Error(j.error || "Erreur API");
return j.data;
}
// ─── DASHBOARD ────────────────────────────────────────────────────────────────
-async function loadDashboard(){
- try{
- const[stats,vehicles,reminders]=await Promise.all([api('stats'),api('vehicles'),api('maintenances')]);
- $('#stat-vehicles').textContent=stats.vehicles;
- $('#stat-maintenances').textContent=stats.maintenances;
- $('#stat-parts').textContent=stats.parts;
- $('#stat-cost').textContent=fmtPrice(parseFloat(stats.total_cost)+parseFloat(stats.total_parts_cost));
+async function loadDashboard() {
+ try {
+ const [stats, vehicles, reminders] = await Promise.all([
+ api("stats"),
+ api("vehicles"),
+ api("maintenances"),
+ ]);
+ $("#stat-vehicles").textContent = stats.vehicles;
+ $("#stat-maintenances").textContent = stats.maintenances;
+ $("#stat-parts").textContent = stats.parts;
+ $("#stat-cost").textContent = fmtPrice(
+ parseFloat(stats.total_cost) + parseFloat(stats.total_parts_cost),
+ );
renderDashboardVehicles(vehicles);
renderReminders(reminders);
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-function renderDashboardVehicles(list){
- const el=$('#dashboard-vehicles');
- if(!list.length){el.innerHTML='
';return;}
- el.innerHTML=list.slice(0,6).map(v=>vehicleCardHTML(v)).join('');
- $$('.vehicle-card',el).forEach(c=>c.addEventListener('click',()=>navigate('vehicle',{id:c.dataset.id})));
+function renderDashboardVehicles(list) {
+ const el = $("#dashboard-vehicles");
+ if (!list.length) {
+ el.innerHTML =
+ '
';
+ return;
+ }
+ el.innerHTML = list
+ .slice(0, 6)
+ .map((v) => vehicleCardHTML(v))
+ .join("");
+ $$(".vehicle-card", el).forEach((c) =>
+ c.addEventListener("click", () =>
+ navigate("vehicle", { id: c.dataset.id }),
+ ),
+ );
}
-function vehicleCardHTML(v){
- const photoHTML=v.photo?'

':'
🚗';
- const fuel=v.fuel_type||'Essence';
- const fuelClass='fuel-'+fuel.replace(/\s/g,'');
- return '
'+
- '
'+photoHTML+''+escHtml(fuel)+'
'+
- '
'+
- '
'+escHtml(v.name)+'
'+
- '
'+escHtml(v.brand)+' '+escHtml(v.model)+(v.year?' · '+v.year:'')+'
'+
- '
'+
- '
🔧 '+(v.maintenance_count||0)+' entretiens
'+
- '
💶 '+fmtPrice(v.total_cost)+'
'+
- '
'+
- '
'+
- '
';
+function vehicleCardHTML(v) {
+ const photoHTML = v.photo
+ ? '

'
+ : '
🚗';
+ const fuel = v.fuel_type || "Essence";
+ const fuelClass = "fuel-" + fuel.replace(/\s/g, "");
+
+ return (
+ '
' +
+ '
' +
+ photoHTML +
+ '' +
+ escHtml(fuel) +
+ "" +
+ "
" +
+ '
' +
+ '
' +
+ escHtml(v.name) +
+ "
" +
+ '
' +
+ escHtml(v.brand) +
+ " " +
+ escHtml(v.model) +
+ (v.year ? " · " + v.year : "") +
+ "
" +
+ '
' +
+ '
🔧 ' +
+ (v.maintenance_count || 0) +
+ " entretiens
" +
+ // Affichage conditionnel de la conso
+ (v.consumption
+ ? '
⛽ ' +
+ v.consumption +
+ " L/100
"
+ : "") +
+ '
💶 ' +
+ fmtPrice(v.total_cost) +
+ "
" +
+ "
" +
+ "
" +
+ '" +
+ "
"
+ );
}
-function renderReminders(list){
- const el=$('#reminders-list');const today=new Date();
- if(!list.length){el.innerHTML='
';return;}
- el.innerHTML='
| Véhicule | Type | Prochaine date | Prochain km | Écart km |
'+
- list.map(r=>{
- const diff=r.next_km&&r.current_km?r.next_km-r.current_km:null;
- const dateOk=r.next_date?new Date(r.next_date)>today:true;
- const kmOk=diff==null||diff>0;
- const cls=(!dateOk||!kmOk)?'badge-red':diff!=null&&diff<2000?'badge-amber':'badge-green';
- return '| '+escHtml(r.vehicle_name)+''+(r.license_plate?' '+escHtml(r.license_plate)+'':'')+' | '+
- ''+escHtml(r.type)+' | '+
- ''+(r.next_date?''+fmtDate(r.next_date)+'':'--')+' | '+
- ''+(r.next_km?fmt(r.next_km)+' km':'--')+' | '+
- ''+(diff!=null?''+(diff>=0?'+':'')+fmt(diff)+' km':'--')+' |
';
- }).join('')+
- '
';
+function renderReminders(list) {
+ const el = $("#reminders-list");
+ const today = new Date();
+ if (!list.length) {
+ el.innerHTML =
+ '
';
+ return;
+ }
+ el.innerHTML =
+ '
| Véhicule | Type | Prochaine date | Prochain km | Écart km |
' +
+ list
+ .map((r) => {
+ const diff =
+ r.next_km && r.current_km ? r.next_km - r.current_km : null;
+ const dateOk = r.next_date ? new Date(r.next_date) > today : true;
+ const kmOk = diff == null || diff > 0;
+ const cls =
+ !dateOk || !kmOk
+ ? "badge-red"
+ : diff != null && diff < 2000
+ ? "badge-amber"
+ : "badge-green";
+ return (
+ "| " +
+ escHtml(r.vehicle_name) +
+ "" +
+ (r.license_plate
+ ? ' ' + escHtml(r.license_plate) + ""
+ : "") +
+ " | " +
+ "" +
+ escHtml(r.type) +
+ " | " +
+ "" +
+ (r.next_date
+ ? '' +
+ fmtDate(r.next_date) +
+ ""
+ : "--") +
+ " | " +
+ "" +
+ (r.next_km ? fmt(r.next_km) + " km" : "--") +
+ " | " +
+ "" +
+ (diff != null
+ ? '' +
+ (diff >= 0 ? "+" : "") +
+ fmt(diff) +
+ " km"
+ : "--") +
+ " |
"
+ );
+ })
+ .join("") +
+ "
";
}
// ─── VEHICLES LIST ────────────────────────────────────────────────────────────
-async function loadVehicles(){
- try{
- const list=await api('vehicles');
- const el=$('#vehicles-grid');
- if(!list.length){el.innerHTML='
🚗
Aucun véhicule. Ajoutez-en un !
';return;}
- el.innerHTML=list.map(v=>vehicleCardHTML(v)).join('');
- $$('.vehicle-card',el).forEach(c=>c.addEventListener('click',()=>navigate('vehicle',{id:c.dataset.id})));
- }catch(e){toast(e.message,'error');}
+async function loadVehicles() {
+ try {
+ const list = await api("vehicles");
+ const el = $("#vehicles-grid");
+ if (!list.length) {
+ el.innerHTML =
+ '
🚗
Aucun véhicule. Ajoutez-en un !
';
+ return;
+ }
+ el.innerHTML = list.map((v) => vehicleCardHTML(v)).join("");
+ $$(".vehicle-card", el).forEach((c) =>
+ c.addEventListener("click", () =>
+ navigate("vehicle", { id: c.dataset.id }),
+ ),
+ );
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── VEHICLE DETAIL ───────────────────────────────────────────────────────────
-async function loadVehicleDetail(id){
- currentVehicleId=id;
- try{
- const[v,maintenances,parts]=await Promise.all([
- api('vehicles','GET',null,'&id='+id),
- api('maintenances','GET',null,'&vehicle_id='+id),
- api('parts','GET',null,'&vehicle_id='+id)
+async function loadVehicleDetail(id) {
+ currentVehicleId = id;
+ try {
+ const [v, maintenances, parts] = await Promise.all([
+ api("vehicles", "GET", null, "&id=" + id),
+ api("maintenances", "GET", null, "&vehicle_id=" + id),
+ api("parts", "GET", null, "&vehicle_id=" + id),
]);
renderVehicleHeader(v);
renderMaintenances(maintenances);
renderVehicleParts(parts);
setVehicleDocTabBadge(v);
await loadVehicleDocuments();
- document.title='GarageManager · '+v.name;
- }catch(e){toast(e.message,'error');}
+ document.title = "GarageManager · " + v.name;
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-function renderVehicleHeader(v){
- const photoHTML=v.photo?'

':'
🚗
';
- const totalCost=(parseFloat(v.stats?.total||0)+parseFloat(v.parts_stats?.total||0)).toFixed(2);
- $('#vehicle-header').innerHTML=
- '';
+function renderVehicleHeader(v) {
+ const photoHTML = v.photo
+ ? '

'
+ : '
🚗
';
+
+ const totalCost = (
+ parseFloat(v.stats?.total || 0) + parseFloat(v.parts_stats?.total || 0)
+ ).toFixed(2);
+
+ $("#vehicle-header").innerHTML =
+ '";
}
-function renderMaintenances(list){
- const el=$('#maintenance-list');
- const totalCost=list.reduce((s,m)=>s+parseFloat(m.cost||0)+parseFloat(m.parts_cost||0),0);
- $('#maintenance-total').textContent=fmtPrice(totalCost);
- if(!list.length){el.innerHTML='
🔧
Aucun entretien enregistré
';return;}
- el.innerHTML='
| Date | Type | Description | Kilométrage | Coût MO | Pièces | Prochain | |
'+
- list.map(m=>''+
- ''+fmtDate(m.date)+' '+ago(m.date)+''+
- (m.documents_count>0?' 📎 '+m.documents_count+'':'')+
- ' | '+
- ''+escHtml(m.type)+' | '+
- ''+(m.description?escHtml(m.description):'--')+' | '+
- ''+(m.km?fmt(m.km)+' km':'--')+' | '+
- ''+fmtPrice(m.cost)+' | '+
- ''+(m.parts_count>0?'🔩 '+m.parts_count+' ('+fmtPrice(m.parts_cost)+')':'--')+' | '+
- ''+(m.next_date?'📅 '+fmtDate(m.next_date):'')+' '+(m.next_km?' 🛣️ '+fmt(m.next_km)+' km':'')+' | '+
- ''+
- ' '+
- ''+
- ' | '+
- '
').join('')+
- '
';
+function renderMaintenances(list) {
+ const el = $("#maintenance-list");
+ const totalCost = list.reduce(
+ (s, m) => s + parseFloat(m.cost || 0) + parseFloat(m.parts_cost || 0),
+ 0,
+ );
+ $("#maintenance-total").textContent = fmtPrice(totalCost);
+ if (!list.length) {
+ el.innerHTML =
+ '
🔧
Aucun entretien enregistré
';
+ return;
+ }
+ el.innerHTML =
+ '
| Date | Type | Description | Kilométrage | Coût MO | Pièces | Prochain | |
' +
+ list
+ .map(
+ (m) =>
+ '' +
+ "" +
+ fmtDate(m.date) +
+ ' ' +
+ ago(m.date) +
+ "" +
+ (m.documents_count > 0
+ ? ' 📎 ' +
+ m.documents_count +
+ ""
+ : "") +
+ " | " +
+ '' +
+ escHtml(m.type) +
+ " | " +
+ '' +
+ (m.description ? escHtml(m.description) : "--") +
+ " | " +
+ "" +
+ (m.km ? fmt(m.km) + " km" : "--") +
+ " | " +
+ "" +
+ fmtPrice(m.cost) +
+ " | " +
+ "" +
+ (m.parts_count > 0
+ ? '🔩 ' +
+ m.parts_count +
+ " (" +
+ fmtPrice(m.parts_cost) +
+ ")"
+ : "--") +
+ " | " +
+ '' +
+ (m.next_date ? "📅 " + fmtDate(m.next_date) : "") +
+ " " +
+ (m.next_km ? " 🛣️ " + fmt(m.next_km) + " km" : "") +
+ " | " +
+ '' +
+ ' ' +
+ '' +
+ " | " +
+ "
",
+ )
+ .join("") +
+ "
";
}
-function renderVehicleParts(list){
- const el=$('#parts-list-vehicle');
- const total=list.reduce((s,p)=>s+parseFloat(p.price||0)*parseInt(p.quantity||1),0);
- $('#parts-total-vehicle').textContent=fmtPrice(total);
- if(!list.length){el.innerHTML='
🔩
Aucune pièce enregistrée
';return;}
- el.innerHTML='
| Photo | Nom | Marque | Référence | Catégorie | Prix unit. | Qté | Total | Entretien | |
'+
- list.map(p=>''+
- ''+(p.photo?' ':'--')+' | '+
- ''+escHtml(p.name)+' | '+
- ''+(p.brand?escHtml(p.brand):'--')+' | '+
- ''+(p.reference?escHtml(p.reference):'--')+' | '+
- ''+escHtml(p.category)+' | '+
- ''+fmtPrice(p.price)+' | '+
- ''+p.quantity+' '+escHtml(p.unit||'pièce')+' | '+
- ''+fmtPrice(parseFloat(p.price||0)*parseInt(p.quantity||1))+' | '+
- ''+(p.maintenance_type?escHtml(p.maintenance_type)+' ('+fmtDate(p.maintenance_date)+')':'--')+' | '+
- ''+
- ' '+
- ''+
- ' | '+
- '
').join('')+
- '
';
+function renderVehicleParts(list) {
+ const el = $("#parts-list-vehicle");
+ const total = list.reduce(
+ (s, p) => s + parseFloat(p.price || 0) * parseInt(p.quantity || 1),
+ 0,
+ );
+ $("#parts-total-vehicle").textContent = fmtPrice(total);
+ if (!list.length) {
+ el.innerHTML =
+ '
🔩
Aucune pièce enregistrée
';
+ return;
+ }
+ el.innerHTML =
+ '
| Photo | Nom | Marque | Référence | Catégorie | Prix unit. | Qté | Total | Entretien | |
' +
+ list
+ .map(
+ (p) =>
+ "" +
+ "" +
+ (p.photo
+ ? ' '
+ : '--') +
+ " | " +
+ "" +
+ escHtml(p.name) +
+ " | " +
+ "" +
+ (p.brand ? escHtml(p.brand) : "--") +
+ " | " +
+ '' +
+ (p.reference ? escHtml(p.reference) : "--") +
+ " | " +
+ '' +
+ escHtml(p.category) +
+ " | " +
+ "" +
+ fmtPrice(p.price) +
+ " | " +
+ "" +
+ p.quantity +
+ " " +
+ escHtml(p.unit || "pièce") +
+ " | " +
+ "" +
+ fmtPrice(parseFloat(p.price || 0) * parseInt(p.quantity || 1)) +
+ " | " +
+ '' +
+ (p.maintenance_type
+ ? escHtml(p.maintenance_type) +
+ " (" +
+ fmtDate(p.maintenance_date) +
+ ")"
+ : "--") +
+ " | " +
+ "" +
+ ' ' +
+ '' +
+ " | " +
+ "
",
+ )
+ .join("") +
+ "
";
}
// ─── MAINTENANCE DETAIL ───────────────────────────────────────────────────────
-async function loadMaintenanceDetail(id){
- currentMaintenanceId=id;
- try{
- const[m,parts]=await Promise.all([
- api('maintenances','GET',null,'&id='+id),
- api('parts','GET',null,'&maintenance_id='+id)
+async function loadMaintenanceDetail(id) {
+ currentMaintenanceId = id;
+ try {
+ const [m, parts] = await Promise.all([
+ api("maintenances", "GET", null, "&id=" + id),
+ api("parts", "GET", null, "&maintenance_id=" + id),
]);
- currentVehicleId=m.vehicle_id;
+ currentVehicleId = m.vehicle_id;
renderMaintenanceDetailHeader(m);
- renderMaintenanceParts(parts,id,m.vehicle_id);
+ renderMaintenanceParts(parts, id, m.vehicle_id);
await loadMaintenanceDocuments();
loadKnownParts(m.vehicle_id);
- const f=document.getElementById('inline-part-form');
- if(f)f.style.display='none';
- }catch(e){toast(e.message,'error');}
+ const f = document.getElementById("inline-part-form");
+ if (f) f.style.display = "none";
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-function renderMaintenanceDetailHeader(m){
- document.getElementById('maintenance-detail-header').innerHTML=
- '';
+function renderMaintenanceDetailHeader(m) {
+ document.getElementById("maintenance-detail-header").innerHTML =
+ '";
}
-function renderMaintenanceParts(list,mid,vid){
- const el=document.getElementById('maintenance-parts-list');
- const cnt=document.getElementById('maint-parts-count');
- if(cnt)cnt.textContent=list.length?list.length+' pièce'+(list.length>1?'s':''):'';
- if(!list.length){el.innerHTML='
';return;}
- const total=list.reduce((s,p)=>s+parseFloat(p.price||0)*parseInt(p.quantity||1),0);
- el.innerHTML='
| Photo | Nom | Marque | Réf. | Catégorie | Prix unit. | Qté | Total | |
'+
- list.map(p=>''+
- ''+(p.photo?' ':'--')+' | '+
- ''+escHtml(p.name)+' | '+
- ''+(p.brand?escHtml(p.brand):'--')+' | '+
- ''+(p.reference?escHtml(p.reference):'--')+' | '+
- ''+escHtml(p.category)+' | '+
- ''+fmtPrice(p.price)+' | '+
- ''+p.quantity+' '+escHtml(p.unit||'pièce')+' | '+
- ''+fmtPrice(parseFloat(p.price||0)*parseInt(p.quantity||1))+' | '+
- ''+
- ' '+
- ''+
- ' | '+
- '
').join('')+
- '| Total pièces | '+fmtPrice(total)+' | |
'+
- '
';
+function renderMaintenanceParts(list, mid, vid) {
+ const el = document.getElementById("maintenance-parts-list");
+ const cnt = document.getElementById("maint-parts-count");
+ if (cnt)
+ cnt.textContent = list.length
+ ? list.length + " pièce" + (list.length > 1 ? "s" : "")
+ : "";
+ if (!list.length) {
+ el.innerHTML =
+ '
';
+ return;
+ }
+ const total = list.reduce(
+ (s, p) => s + parseFloat(p.price || 0) * parseInt(p.quantity || 1),
+ 0,
+ );
+ el.innerHTML =
+ '
| Photo | Nom | Marque | Réf. | Catégorie | Prix unit. | Qté | Total | |
' +
+ list
+ .map(
+ (p) =>
+ "" +
+ "" +
+ (p.photo
+ ? ' '
+ : "--") +
+ " | " +
+ "" +
+ escHtml(p.name) +
+ " | " +
+ "" +
+ (p.brand ? escHtml(p.brand) : "--") +
+ " | " +
+ '' +
+ (p.reference ? escHtml(p.reference) : "--") +
+ " | " +
+ '' +
+ escHtml(p.category) +
+ " | " +
+ "" +
+ fmtPrice(p.price) +
+ " | " +
+ "" +
+ p.quantity +
+ " " +
+ escHtml(p.unit || "pièce") +
+ " | " +
+ "" +
+ fmtPrice(parseFloat(p.price || 0) * parseInt(p.quantity || 1)) +
+ " | " +
+ "" +
+ ' ' +
+ '' +
+ " | " +
+ "
",
+ )
+ .join("") +
+ '| Total pièces | ' +
+ fmtPrice(total) +
+ " | |
" +
+ "
";
}
-async function deleteMaintPart(id,mid,vid){
- if(!confirm('Supprimer cette pièce ?'))return;
- try{
- await api('parts','DELETE',null,'&id='+id);
- toast('Pièce supprimée');
- const parts=await api('parts','GET',null,'&maintenance_id='+mid);
- renderMaintenanceParts(parts,mid,vid);
- }catch(e){toast(e.message,'error');}
+async function deleteMaintPart(id, mid, vid) {
+ if (!confirm("Supprimer cette pièce ?")) return;
+ try {
+ await api("parts", "DELETE", null, "&id=" + id);
+ toast("Pièce supprimée");
+ const parts = await api("parts", "GET", null, "&maintenance_id=" + mid);
+ renderMaintenanceParts(parts, mid, vid);
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── INLINE PART FORM ────────────────────────────────────────────────────────
-function toggleInlinePartForm(){
- const f=document.getElementById('inline-part-form');
- if(!f)return;
- const visible=f.style.display==='block';
- f.style.display=visible?'none':'block';
- if(!visible)document.getElementById('ipart-name')?.focus();
+function toggleInlinePartForm() {
+ const f = document.getElementById("inline-part-form");
+ if (!f) return;
+ const visible = f.style.display === "block";
+ f.style.display = visible ? "none" : "block";
+ if (!visible) document.getElementById("ipart-name")?.focus();
}
-async function loadKnownParts(vehicleId){
- try{
- const parts=await api('parts','GET',null,'&vehicle_id='+vehicleId);
- const el=document.getElementById('known-parts-list');
- if(!el)return;
- if(!parts.length){el.innerHTML='
Aucune pièce connue pour ce véhicule
';return;}
- const seen=new Set();
- const unique=parts.filter(p=>{if(seen.has(p.name))return false;seen.add(p.name);return true;});
- const wrap=document.createElement('div');
- wrap.className='scroll-list';
- unique.forEach((p,i)=>{
- const lbl=document.createElement('label');
- lbl.className='scroll-list-item';
- lbl.innerHTML=
- '
'+
- '
'+escHtml(p.name)+''+
- (p.reference?'
'+escHtml(p.reference)+'':'')+
- (p.brand?'
'+escHtml(p.brand)+'':'')+
- '
'+fmtPrice(p.price)+'';
- lbl.querySelector('input').addEventListener('change',()=>{
- const n=document.getElementById('ipart-name');const b=document.getElementById('ipart-brand');
- const r=document.getElementById('ipart-reference');const pr=document.getElementById('ipart-price');
- if(n)n.value=p.name;
- if(b)b.value=p.brand||'';
- if(r)r.value=p.reference||'';
- if(pr){pr.value=p.price;updateIPriceTTC();}
+async function loadKnownParts(vehicleId) {
+ try {
+ const parts = await api("parts", "GET", null, "&vehicle_id=" + vehicleId);
+ const el = document.getElementById("known-parts-list");
+ if (!el) return;
+ if (!parts.length) {
+ el.innerHTML =
+ '
Aucune pièce connue pour ce véhicule
';
+ return;
+ }
+ const seen = new Set();
+ const unique = parts.filter((p) => {
+ if (seen.has(p.name)) return false;
+ seen.add(p.name);
+ return true;
+ });
+ const wrap = document.createElement("div");
+ wrap.className = "scroll-list";
+ unique.forEach((p, i) => {
+ const lbl = document.createElement("label");
+ lbl.className = "scroll-list-item";
+ lbl.innerHTML =
+ '
' +
+ '
' +
+ escHtml(p.name) +
+ "" +
+ (p.reference
+ ? '
' + escHtml(p.reference) + ""
+ : "") +
+ (p.brand
+ ? '
' + escHtml(p.brand) + ""
+ : "") +
+ '
' +
+ fmtPrice(p.price) +
+ "";
+ lbl.querySelector("input").addEventListener("change", () => {
+ const n = document.getElementById("ipart-name");
+ const b = document.getElementById("ipart-brand");
+ const r = document.getElementById("ipart-reference");
+ const pr = document.getElementById("ipart-price");
+ if (n) n.value = p.name;
+ if (b) b.value = p.brand || "";
+ if (r) r.value = p.reference || "";
+ if (pr) {
+ pr.value = p.price;
+ updateIPriceTTC();
+ }
});
wrap.appendChild(lbl);
});
- el.innerHTML='';
+ el.innerHTML = "";
el.appendChild(wrap);
- }catch(e){}
+ } catch (e) {}
}
-function updateIPriceTTC(){
- const ht=document.getElementById('ipart-prix-ht')?.checked;
- const price=parseFloat(document.getElementById('ipart-price')?.value)||0;
- const preview=document.getElementById('ipart-ttc-preview');
- if(!preview)return;
- if(ht&&price>0){preview.textContent='→ Prix TTC : '+(price*1.2).toFixed(2)+' €';preview.style.display='block';}
- else{preview.style.display='none';}
+function updateIPriceTTC() {
+ const ht = document.getElementById("ipart-prix-ht")?.checked;
+ const price = parseFloat(document.getElementById("ipart-price")?.value) || 0;
+ const preview = document.getElementById("ipart-ttc-preview");
+ if (!preview) return;
+ if (ht && price > 0) {
+ preview.textContent = "→ Prix TTC : " + (price * 1.2).toFixed(2) + " €";
+ preview.style.display = "block";
+ } else {
+ preview.style.display = "none";
+ }
}
-async function saveInlinePart(){
- const name=document.getElementById('ipart-name')?.value.trim();
- if(!name){toast('Nom requis','error');return;}
- let price=parseFloat(document.getElementById('ipart-price')?.value)||0;
- if(document.getElementById('ipart-prix-ht')?.checked)price=parseFloat((price*1.2).toFixed(2));
- const fd=new FormData();
- fd.append('name',name);
- fd.append('vehicle_id',currentVehicleId||'');
- fd.append('maintenance_id',currentMaintenanceId||'');
- fd.append('price',price);
- fd.append('quantity',parseInt(document.getElementById('ipart-quantity')?.value)||1);
- fd.append('category','Autre');
- fd.append('unit','pièce');
- const brand=document.getElementById('ipart-brand')?.value.trim();
- const ref=document.getElementById('ipart-reference')?.value.trim();
- if(brand)fd.append('brand',brand);
- if(ref)fd.append('reference',ref);
- const photoFile=document.getElementById('ipart-photo')?.files[0];
- if(photoFile)fd.append('photo',photoFile);
- try{
- await api('parts','POST',fd);
- toast('Pièce ajoutée');
- ['ipart-name','ipart-brand','ipart-reference'].forEach(id=>{const el=document.getElementById(id);if(el)el.value='';});
- const pr=document.getElementById('ipart-price');if(pr)pr.value='0';
- const qty=document.getElementById('ipart-quantity');if(qty)qty.value='1';
- const ht=document.getElementById('ipart-prix-ht');if(ht)ht.checked=false;
- const prev=document.getElementById('ipart-ttc-preview');if(prev)prev.style.display='none';
- const ph=document.getElementById('ipart-photo');if(ph)ph.value='';
- const parts=await api('parts','GET',null,'&maintenance_id='+currentMaintenanceId);
- renderMaintenanceParts(parts,currentMaintenanceId,currentVehicleId);
- }catch(e){toast(e.message,'error');}
+async function saveInlinePart() {
+ const name = document.getElementById("ipart-name")?.value.trim();
+ if (!name) {
+ toast("Nom requis", "error");
+ return;
+ }
+ let price = parseFloat(document.getElementById("ipart-price")?.value) || 0;
+ if (document.getElementById("ipart-prix-ht")?.checked)
+ price = parseFloat((price * 1.2).toFixed(2));
+ const fd = new FormData();
+ fd.append("name", name);
+ fd.append("vehicle_id", currentVehicleId || "");
+ fd.append("maintenance_id", currentMaintenanceId || "");
+ fd.append("price", price);
+ fd.append(
+ "quantity",
+ parseInt(document.getElementById("ipart-quantity")?.value) || 1,
+ );
+ fd.append("category", "Autre");
+ fd.append("unit", "pièce");
+ const brand = document.getElementById("ipart-brand")?.value.trim();
+ const ref = document.getElementById("ipart-reference")?.value.trim();
+ if (brand) fd.append("brand", brand);
+ if (ref) fd.append("reference", ref);
+ const photoFile = document.getElementById("ipart-photo")?.files[0];
+ if (photoFile) fd.append("photo", photoFile);
+ try {
+ await api("parts", "POST", fd);
+ toast("Pièce ajoutée");
+ ["ipart-name", "ipart-brand", "ipart-reference"].forEach((id) => {
+ const el = document.getElementById(id);
+ if (el) el.value = "";
+ });
+ const pr = document.getElementById("ipart-price");
+ if (pr) pr.value = "0";
+ const qty = document.getElementById("ipart-quantity");
+ if (qty) qty.value = "1";
+ const ht = document.getElementById("ipart-prix-ht");
+ if (ht) ht.checked = false;
+ const prev = document.getElementById("ipart-ttc-preview");
+ if (prev) prev.style.display = "none";
+ const ph = document.getElementById("ipart-photo");
+ if (ph) ph.value = "";
+ const parts = await api(
+ "parts",
+ "GET",
+ null,
+ "&maintenance_id=" + currentMaintenanceId,
+ );
+ renderMaintenanceParts(parts, currentMaintenanceId, currentVehicleId);
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── ALL PARTS ────────────────────────────────────────────────────────────────
-async function loadParts(){
- try{
- const list=await api('parts');
- const el=$('#all-parts-list');
- const total=list.reduce((s,p)=>s+parseFloat(p.price||0)*parseInt(p.quantity||1),0);
- $('#all-parts-total').textContent=fmtPrice(total);
- $('#all-parts-count').textContent=list.length;
- if(!list.length){el.innerHTML='
';return;}
- const byVehicle={};
- list.forEach(p=>{const key=p.vehicle_name||'Sans véhicule';if(!byVehicle[key])byVehicle[key]=[];byVehicle[key].push(p);});
- let html='';
- for(const[vname,parts] of Object.entries(byVehicle)){
- const vtotal=parts.reduce((s,p)=>s+parseFloat(p.price||0)*parseInt(p.quantity||1),0);
- html+='
'+
- ''+
- '
| Photo | Nom | Marque | Référence | Catégorie | Prix unit. | Qté | Total | Entretien | |
'+
- parts.map(p=>''+
- ''+(p.photo?' ':'--')+' | '+
- ''+escHtml(p.name)+' | '+
- ''+(p.brand?escHtml(p.brand):'--')+' | '+
- ''+(p.reference?escHtml(p.reference):'--')+' | '+
- ''+escHtml(p.category)+' | '+
- ''+fmtPrice(p.price)+' | '+
- ''+p.quantity+' '+escHtml(p.unit||'pièce')+' | '+
- ''+fmtPrice(parseFloat(p.price||0)*parseInt(p.quantity||1))+' | '+
- ''+(p.maintenance_type?escHtml(p.maintenance_type)+' ('+fmtDate(p.maintenance_date)+')':'--')+' | '+
- ''+
- ' '+
- ''+
- ' | '+
- '
').join('')+
- '
';
+async function loadParts() {
+ try {
+ const list = await api("parts");
+ const el = $("#all-parts-list");
+ const total = list.reduce(
+ (s, p) => s + parseFloat(p.price || 0) * parseInt(p.quantity || 1),
+ 0,
+ );
+ $("#all-parts-total").textContent = fmtPrice(total);
+ $("#all-parts-count").textContent = list.length;
+ if (!list.length) {
+ el.innerHTML =
+ '
';
+ return;
}
- el.innerHTML=html;
- }catch(e){toast(e.message,'error');}
+ const byVehicle = {};
+ list.forEach((p) => {
+ const key = p.vehicle_name || "Sans véhicule";
+ if (!byVehicle[key]) byVehicle[key] = [];
+ byVehicle[key].push(p);
+ });
+ let html = "";
+ for (const [vname, parts] of Object.entries(byVehicle)) {
+ const vtotal = parts.reduce(
+ (s, p) => s + parseFloat(p.price || 0) * parseInt(p.quantity || 1),
+ 0,
+ );
+ html +=
+ '
' +
+ '" +
+ '
| Photo | Nom | Marque | Référence | Catégorie | Prix unit. | Qté | Total | Entretien | |
' +
+ parts
+ .map(
+ (p) =>
+ "" +
+ "" +
+ (p.photo
+ ? ' '
+ : "--") +
+ " | " +
+ "" +
+ escHtml(p.name) +
+ " | " +
+ "" +
+ (p.brand ? escHtml(p.brand) : "--") +
+ " | " +
+ '' +
+ (p.reference ? escHtml(p.reference) : "--") +
+ " | " +
+ '' +
+ escHtml(p.category) +
+ " | " +
+ "" +
+ fmtPrice(p.price) +
+ " | " +
+ "" +
+ p.quantity +
+ " " +
+ escHtml(p.unit || "pièce") +
+ " | " +
+ "" +
+ fmtPrice(parseFloat(p.price || 0) * parseInt(p.quantity || 1)) +
+ " | " +
+ '' +
+ (p.maintenance_type
+ ? escHtml(p.maintenance_type) +
+ " (" +
+ fmtDate(p.maintenance_date) +
+ ")"
+ : "--") +
+ " | " +
+ "" +
+ ' ' +
+ '' +
+ " | " +
+ "
",
+ )
+ .join("") +
+ "
";
+ }
+ el.innerHTML = html;
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── ALL MAINTENANCES ─────────────────────────────────────────────────────────
-async function loadAllMaintenances(){
- try{
- const vehicles=await api('vehicles');
- const el=$('#all-maintenances-list');
- if(!vehicles.length){el.innerHTML='
';return;}
- let totalAll=0,countAll=0,html='';
- for(const v of vehicles){
- const mlist=await api('maintenances',undefined,null,'&vehicle_id='+v.id);
- if(!mlist.length)continue;
- const total=mlist.reduce((s,m)=>s+parseFloat(m.cost||0)+parseFloat(m.parts_cost||0),0);
- totalAll+=total;countAll+=mlist.length;
- html+='
'+
- ''+
- '
| Date | Type | Description | Km | Coût M.O. | Pièces | Total | Mécanicien | |
'+
- mlist.map(m=>''+
- '| '+fmtDate(m.date)+' | '+
- ''+escHtml(m.type)+' | '+
- ''+(m.description?escHtml(m.description):'--')+' | '+
- ''+(m.km?fmt(m.km)+' km':'--')+' | '+
- ''+fmtPrice(m.cost)+' | '+
- ''+(m.parts_count>0?m.parts_count+' ('+fmtPrice(m.parts_cost)+')':'--')+' | '+
- ''+fmtPrice(parseFloat(m.cost||0)+parseFloat(m.parts_cost||0))+' | '+
- ''+(m.mechanic?escHtml(m.mechanic):'--')+' | '+
- ''+
- ''+
- ' | '+
- '
').join('')+
- '
';
+async function loadAllMaintenances() {
+ try {
+ const vehicles = await api("vehicles");
+ const el = $("#all-maintenances-list");
+ if (!vehicles.length) {
+ el.innerHTML =
+ '
';
+ return;
}
- $('#all-maint-count').textContent=countAll;
- $('#all-maint-total').textContent=fmtPrice(totalAll);
- el.innerHTML=html||'
';
- }catch(e){console.error(e);}
+ let totalAll = 0,
+ countAll = 0,
+ html = "";
+ for (const v of vehicles) {
+ const mlist = await api(
+ "maintenances",
+ undefined,
+ null,
+ "&vehicle_id=" + v.id,
+ );
+ if (!mlist.length) continue;
+ const total = mlist.reduce(
+ (s, m) => s + parseFloat(m.cost || 0) + parseFloat(m.parts_cost || 0),
+ 0,
+ );
+ totalAll += total;
+ countAll += mlist.length;
+ html +=
+ '
' +
+ '" +
+ '
| Date | Type | Description | Km | Coût M.O. | Pièces | Total | Mécanicien | |
' +
+ mlist
+ .map(
+ (m) =>
+ '' +
+ "| " +
+ fmtDate(m.date) +
+ " | " +
+ '' +
+ escHtml(m.type) +
+ " | " +
+ "" +
+ (m.description ? escHtml(m.description) : "--") +
+ " | " +
+ "" +
+ (m.km ? fmt(m.km) + " km" : "--") +
+ " | " +
+ "" +
+ fmtPrice(m.cost) +
+ " | " +
+ "" +
+ (m.parts_count > 0
+ ? m.parts_count + " (" + fmtPrice(m.parts_cost) + ")"
+ : "--") +
+ " | " +
+ "" +
+ fmtPrice(
+ parseFloat(m.cost || 0) + parseFloat(m.parts_cost || 0),
+ ) +
+ " | " +
+ "" +
+ (m.mechanic ? escHtml(m.mechanic) : "--") +
+ " | " +
+ '' +
+ '' +
+ " | " +
+ "
",
+ )
+ .join("") +
+ "
";
+ }
+ $("#all-maint-count").textContent = countAll;
+ $("#all-maint-total").textContent = fmtPrice(totalAll);
+ el.innerHTML =
+ html ||
+ '
';
+ } catch (e) {
+ console.error(e);
+ }
}
// ─── MODALS ───────────────────────────────────────────────────────────────────
-function openModal(id){document.getElementById(id).classList.add('show');}
-function closeModal(id){document.getElementById(id).classList.remove('show');}
+function openModal(id) {
+ document.getElementById(id).classList.add("show");
+}
+function closeModal(id) {
+ document.getElementById(id).classList.remove("show");
+}
// ─── TVA ──────────────────────────────────────────────────────────────────────
-function updatePartPriceTTC(){
- const ht=document.getElementById('part-prix-ht')?.checked;
- const price=parseFloat(document.getElementById('part-price')?.value)||0;
- const preview=document.getElementById('part-ttc-preview');
- if(!preview)return;
- if(ht&&price>0){preview.textContent='→ Prix TTC : '+(price*1.2).toFixed(2)+' €';preview.style.display='block';}
- else{preview.style.display='none';}
+function updatePartPriceTTC() {
+ const ht = document.getElementById("part-prix-ht")?.checked;
+ const price = parseFloat(document.getElementById("part-price")?.value) || 0;
+ const preview = document.getElementById("part-ttc-preview");
+ if (!preview) return;
+ if (ht && price > 0) {
+ preview.textContent = "→ Prix TTC : " + (price * 1.2).toFixed(2) + " €";
+ preview.style.display = "block";
+ } else {
+ preview.style.display = "none";
+ }
}
// ─── VEHICLE CRUD ─────────────────────────────────────────────────────────────
-function openAddVehicle(){
- $('#form-vehicle').reset();$('#form-vehicle-id').value='';
- $('#modal-vehicle-title').textContent='Ajouter un véhicule';
- $('#vehicle-photo-preview').src='';$('#vehicle-photo-preview').style.display='none';
- openModal('modal-vehicle');
+function openAddVehicle() {
+ $("#form-vehicle").reset();
+ $("#form-vehicle-id").value = "";
+ $("#modal-vehicle-title").textContent = "Ajouter un véhicule";
+ $("#vehicle-photo-preview").src = "";
+ $("#vehicle-photo-preview").style.display = "none";
+ openModal("modal-vehicle");
}
-function openEditVehicle(id){
- api('vehicles','GET',null,'&id='+id).then(v=>{
- $('#form-vehicle-id').value=v.id;
- $('#modal-vehicle-title').textContent='Modifier le véhicule';
- ['name','brand','model','year','license_plate','vin','color','purchase_date','purchase_price','current_km','notes'].forEach(f=>{
- const el=$('#vehicle-'+f.replace(/_/g,'-'));
- if(el)el.value=v[f]??'';
- });
- setSelectVal('vehicle-fuel-type', v.fuel_type);
- if(v.photo){$('#vehicle-photo-preview').src=UPLOADS+v.photo;$('#vehicle-photo-preview').style.display='block';}
- else{$('#vehicle-photo-preview').style.display='none';}
- openModal('modal-vehicle');
- }).catch(e=>toast(e.message,'error'));
+function openEditVehicle(id) {
+ api("vehicles", "GET", null, "&id=" + id)
+ .then((v) => {
+ $("#form-vehicle-id").value = v.id;
+ $("#modal-vehicle-title").textContent = "Modifier le véhicule";
+ [
+ "name",
+ "brand",
+ "model",
+ "year",
+ "license_plate",
+ "vin",
+ "color",
+ "purchase_date",
+ "purchase_price",
+ "current_km",
+ "consumption",
+ "notes",
+ ].forEach((f) => {
+ const el = $("#vehicle-" + f.replace(/_/g, "-"));
+ if (el) el.value = v[f] ?? "";
+ });
+ setSelectVal("vehicle-fuel-type", v.fuel_type);
+ if (v.photo) {
+ $("#vehicle-photo-preview").src = UPLOADS + v.photo;
+ $("#vehicle-photo-preview").style.display = "block";
+ } else {
+ $("#vehicle-photo-preview").style.display = "none";
+ }
+ openModal("modal-vehicle");
+ })
+ .catch((e) => toast(e.message, "error"));
}
-async function saveVehicle(){
- const id=$('#form-vehicle-id').value;
- const fd=new FormData($('#form-vehicle'));
- try{
- if(id){
- const d={};for(const[k,v]of fd.entries())d[k]=v;
- await api('vehicles','PUT',d,'&id='+id);
- const photoFile=$('#vehicle-photo').files[0];
- if(photoFile){const pfd=new FormData();pfd.append('photo',photoFile);await fetch(API+'?action=upload_vehicle_photo&id='+id,{method:'POST',body:pfd});}
- toast('Véhicule modifié');
- }else{
- await api('vehicles','POST',fd);
- toast('Véhicule ajouté');
+async function saveVehicle() {
+ const id = $("#form-vehicle-id").value;
+ const fd = new FormData($("#form-vehicle"));
+ try {
+ if (id) {
+ const d = {};
+ for (const [k, v] of fd.entries()) d[k] = v;
+ await api("vehicles", "PUT", d, "&id=" + id);
+ const photoFile = $("#vehicle-photo").files[0];
+ if (photoFile) {
+ const pfd = new FormData();
+ pfd.append("photo", photoFile);
+ await fetch(API + "?action=upload_vehicle_photo&id=" + id, {
+ method: "POST",
+ body: pfd,
+ });
+ }
+ toast("Véhicule modifié");
+ } else {
+ await api("vehicles", "POST", fd);
+ toast("Véhicule ajouté");
}
- closeModal('modal-vehicle');
- if(currentPage==='vehicles')loadVehicles();
- else if(currentPage==='vehicle')loadVehicleDetail(currentVehicleId);
+ closeModal("modal-vehicle");
+ if (currentPage === "vehicles") loadVehicles();
+ else if (currentPage === "vehicle") loadVehicleDetail(currentVehicleId);
else loadDashboard();
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-async function deleteVehicle(id){
- if(!confirm('Supprimer ce véhicule et tout son historique ?'))return;
- try{await api('vehicles','DELETE',null,'&id='+id);toast('Véhicule supprimé');navigate('vehicles');}catch(e){toast(e.message,'error');}
+async function deleteVehicle(id) {
+ if (!confirm("Supprimer ce véhicule et tout son historique ?")) return;
+ try {
+ await api("vehicles", "DELETE", null, "&id=" + id);
+ toast("Véhicule supprimé");
+ navigate("vehicles");
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── MAINTENANCE CRUD ─────────────────────────────────────────────────────────
-function openAddMaintenance(){
- $('#form-maintenance').reset();$('#form-maintenance-id').value='';
- $('#maintenance-vehicle-id').value=currentVehicleId||'';
- $('#modal-maintenance-title').textContent='Ajouter un entretien';
- $('#maintenance-date').value=new Date().toISOString().slice(0,10);
- openModal('modal-maintenance');
+function openAddMaintenance() {
+ $("#form-maintenance").reset();
+ $("#form-maintenance-id").value = "";
+ $("#maintenance-vehicle-id").value = currentVehicleId || "";
+ $("#modal-maintenance-title").textContent = "Ajouter un entretien";
+ $("#maintenance-date").value = new Date().toISOString().slice(0, 10);
+ openModal("modal-maintenance");
}
-function setSelectVal(elId, val){
- const el=document.getElementById(elId); if(!el||val==null)return;
- el.value=val;
- if(el.value!==String(val)&&val!==''){const o=new Option(val,val);el.add(o);el.value=val;}
+function setSelectVal(elId, val) {
+ const el = document.getElementById(elId);
+ if (!el || val == null) return;
+ el.value = val;
+ if (el.value !== String(val) && val !== "") {
+ const o = new Option(val, val);
+ el.add(o);
+ el.value = val;
+ }
}
-function openEditMaintenance(id){
- api('maintenances','GET',null,'&id='+id).then(m=>{
- $('#form-maintenance-id').value=m.id;
- $('#maintenance-vehicle-id').value=m.vehicle_id;
- $('#modal-maintenance-title').textContent="Modifier l'entretien";
- setSelectVal('maintenance-type', m.type);
- const map={description:'maintenance-description',date:'maintenance-date',
- km:'maintenance-km',cost:'maintenance-cost',mechanic:'maintenance-mechanic',
- garage_name:'maintenance-garage',next_date:'maintenance-next-date',next_km:'maintenance-next-km',notes:'maintenance-notes'};
- for(const[k,elId] of Object.entries(map)){const el=document.getElementById(elId);if(el)el.value=m[k]??'';}
- openModal('modal-maintenance');
- }).catch(e=>toast(e.message,'error'));
+function openEditMaintenance(id) {
+ api("maintenances", "GET", null, "&id=" + id)
+ .then((m) => {
+ $("#form-maintenance-id").value = m.id;
+ $("#maintenance-vehicle-id").value = m.vehicle_id;
+ $("#modal-maintenance-title").textContent = "Modifier l'entretien";
+ setSelectVal("maintenance-type", m.type);
+ const map = {
+ description: "maintenance-description",
+ date: "maintenance-date",
+ km: "maintenance-km",
+ cost: "maintenance-cost",
+ mechanic: "maintenance-mechanic",
+ garage_name: "maintenance-garage",
+ next_date: "maintenance-next-date",
+ next_km: "maintenance-next-km",
+ notes: "maintenance-notes",
+ };
+ for (const [k, elId] of Object.entries(map)) {
+ const el = document.getElementById(elId);
+ if (el) el.value = m[k] ?? "";
+ }
+ openModal("modal-maintenance");
+ })
+ .catch((e) => toast(e.message, "error"));
}
-async function saveMaintenance(){
- const id=$('#form-maintenance-id').value;
- const d={};new FormData($('#form-maintenance')).forEach((v,k)=>d[k]=v);
- try{
- if(id){await api('maintenances','PUT',d,'&id='+id);toast('Entretien modifié');}
- else{await api('maintenances','POST',d);toast('Entretien ajouté');}
- closeModal('modal-maintenance');
- if(currentPage==='maintenance')loadMaintenanceDetail(currentMaintenanceId);
+async function saveMaintenance() {
+ const id = $("#form-maintenance-id").value;
+ const d = {};
+ new FormData($("#form-maintenance")).forEach((v, k) => (d[k] = v));
+ try {
+ if (id) {
+ await api("maintenances", "PUT", d, "&id=" + id);
+ toast("Entretien modifié");
+ } else {
+ await api("maintenances", "POST", d);
+ toast("Entretien ajouté");
+ }
+ closeModal("modal-maintenance");
+ if (currentPage === "maintenance")
+ loadMaintenanceDetail(currentMaintenanceId);
else loadVehicleDetail(currentVehicleId);
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-async function deleteMaintenance(id){
- if(!confirm('Supprimer cet entretien ?'))return;
- try{
- await api('maintenances','DELETE',null,'&id='+id);
- toast('Entretien supprimé');
- if(currentPage==='maintenance')navigate('vehicle',{id:currentVehicleId});
+async function deleteMaintenance(id) {
+ if (!confirm("Supprimer cet entretien ?")) return;
+ try {
+ await api("maintenances", "DELETE", null, "&id=" + id);
+ toast("Entretien supprimé");
+ if (currentPage === "maintenance")
+ navigate("vehicle", { id: currentVehicleId });
else loadVehicleDetail(currentVehicleId);
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── PARTS CRUD ───────────────────────────────────────────────────────────────
-function openAddPart(){
- $('#form-part').reset();$('#form-part-id').value='';
- $('#part-vehicle-id').value=currentVehicleId||'';
- $('#part-maintenance-id').value=currentMaintenanceId||'';
- $('#modal-part-title').textContent='Ajouter une pièce';
- $('#part-photo-preview').src='';$('#part-photo-preview').style.display='none';
- const ht=document.getElementById('part-prix-ht');if(ht)ht.checked=false;
- const prev=document.getElementById('part-ttc-preview');if(prev)prev.style.display='none';
- openModal('modal-part');
+function openAddPart() {
+ $("#form-part").reset();
+ $("#form-part-id").value = "";
+ $("#part-vehicle-id").value = currentVehicleId || "";
+ $("#part-maintenance-id").value = currentMaintenanceId || "";
+ $("#modal-part-title").textContent = "Ajouter une pièce";
+ $("#part-photo-preview").src = "";
+ $("#part-photo-preview").style.display = "none";
+ const ht = document.getElementById("part-prix-ht");
+ if (ht) ht.checked = false;
+ const prev = document.getElementById("part-ttc-preview");
+ if (prev) prev.style.display = "none";
+ openModal("modal-part");
}
-function openEditPart(id){
- api('parts','GET',null,'&id='+id).then(p=>{
- $('#form-part-id').value=p.id;
- $('#part-vehicle-id').value=p.vehicle_id||'';
- $('#part-maintenance-id').value=p.maintenance_id||'';
- $('#modal-part-title').textContent='Modifier la pièce';
- const map={name:'part-name',brand:'part-brand',reference:'part-reference',price:'part-price',quantity:'part-quantity',notes:'part-notes'};
- for(const[k,elId] of Object.entries(map)){const el=document.getElementById(elId);if(el)el.value=p[k]??'';}
- setSelectVal('part-category', p.category);
- const ht=document.getElementById('part-prix-ht');if(ht)ht.checked=false;
- const prev=document.getElementById('part-ttc-preview');if(prev)prev.style.display='none';
- if(p.photo){$('#part-photo-preview').src=UPLOADS+p.photo;$('#part-photo-preview').style.display='block';}
- else{$('#part-photo-preview').style.display='none';}
- openModal('modal-part');
- }).catch(e=>toast(e.message,'error'));
+function openEditPart(id) {
+ api("parts", "GET", null, "&id=" + id)
+ .then((p) => {
+ $("#form-part-id").value = p.id;
+ $("#part-vehicle-id").value = p.vehicle_id || "";
+ $("#part-maintenance-id").value = p.maintenance_id || "";
+ $("#modal-part-title").textContent = "Modifier la pièce";
+ const map = {
+ name: "part-name",
+ brand: "part-brand",
+ reference: "part-reference",
+ price: "part-price",
+ quantity: "part-quantity",
+ notes: "part-notes",
+ };
+ for (const [k, elId] of Object.entries(map)) {
+ const el = document.getElementById(elId);
+ if (el) el.value = p[k] ?? "";
+ }
+ setSelectVal("part-category", p.category);
+ const ht = document.getElementById("part-prix-ht");
+ if (ht) ht.checked = false;
+ const prev = document.getElementById("part-ttc-preview");
+ if (prev) prev.style.display = "none";
+ if (p.photo) {
+ $("#part-photo-preview").src = UPLOADS + p.photo;
+ $("#part-photo-preview").style.display = "block";
+ } else {
+ $("#part-photo-preview").style.display = "none";
+ }
+ openModal("modal-part");
+ })
+ .catch((e) => toast(e.message, "error"));
}
-async function savePart(){
- const id=$('#form-part-id').value;
- const fd=new FormData($('#form-part'));
- if(document.getElementById('part-prix-ht')?.checked){
- const htPrice=parseFloat(fd.get('price'))||0;
- fd.set('price',(htPrice*1.2).toFixed(2));
+async function savePart() {
+ const id = $("#form-part-id").value;
+ const fd = new FormData($("#form-part"));
+ if (document.getElementById("part-prix-ht")?.checked) {
+ const htPrice = parseFloat(fd.get("price")) || 0;
+ fd.set("price", (htPrice * 1.2).toFixed(2));
}
- try{
- if(id){
- const d={};for(const[k,v]of fd.entries())d[k]=v;
- await api('parts','PUT',d,'&id='+id);
- const photoFile=$('#part-photo').files[0];
- if(photoFile){const pfd=new FormData();pfd.append('photo',photoFile);await fetch(API+'?action=upload_part_photo&id='+id,{method:'POST',body:pfd});}
- toast('Pièce modifiée');
- }else{
- await api('parts','POST',fd);
- toast('Pièce ajoutée');
+ try {
+ if (id) {
+ const d = {};
+ for (const [k, v] of fd.entries()) d[k] = v;
+ await api("parts", "PUT", d, "&id=" + id);
+ const photoFile = $("#part-photo").files[0];
+ if (photoFile) {
+ const pfd = new FormData();
+ pfd.append("photo", photoFile);
+ await fetch(API + "?action=upload_part_photo&id=" + id, {
+ method: "POST",
+ body: pfd,
+ });
+ }
+ toast("Pièce modifiée");
+ } else {
+ await api("parts", "POST", fd);
+ toast("Pièce ajoutée");
}
- closeModal('modal-part');
- if(currentPage==='maintenance')loadMaintenanceDetail(currentMaintenanceId);
- else if(currentPage==='vehicle')loadVehicleDetail(currentVehicleId);
+ closeModal("modal-part");
+ if (currentPage === "maintenance")
+ loadMaintenanceDetail(currentMaintenanceId);
+ else if (currentPage === "vehicle") loadVehicleDetail(currentVehicleId);
else loadParts();
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-async function deletePart(id){
- if(!confirm('Supprimer cette pièce ?'))return;
- try{
- await api('parts','DELETE',null,'&id='+id);
- toast('Pièce supprimée');
- if(currentPage==='maintenance')loadMaintenanceDetail(currentMaintenanceId);
- else if(currentPage==='vehicle')loadVehicleDetail(currentVehicleId);
+async function deletePart(id) {
+ if (!confirm("Supprimer cette pièce ?")) return;
+ try {
+ await api("parts", "DELETE", null, "&id=" + id);
+ toast("Pièce supprimée");
+ if (currentPage === "maintenance")
+ loadMaintenanceDetail(currentMaintenanceId);
+ else if (currentPage === "vehicle") loadVehicleDetail(currentVehicleId);
else loadParts();
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── UPLOAD PHOTO ─────────────────────────────────────────────────────────────
-function openUploadPhoto(id){$('#upload-vehicle-id').value=id;openModal('modal-upload-photo');}
-async function doUploadPhoto(){
- const id=$('#upload-vehicle-id').value;
- const file=$('#upload-photo-file').files[0];
- if(!file){toast('Choisissez une photo','error');return;}
- const fd=new FormData();fd.append('photo',file);
- try{
- await fetch(API+'?action=upload_vehicle_photo&id='+id,{method:'POST',body:fd});
- toast('Photo mise à jour');closeModal('modal-upload-photo');
- loadVehicleDetail(id);
- }catch(e){toast(e.message,'error');}
+function openUploadPhoto(id) {
+ $("#upload-vehicle-id").value = id;
+ openModal("modal-upload-photo");
}
-
-function setVehicleDocTabBadge(v){
- const el=document.getElementById('vehicle-docs-tab-count');
- if(!el)return;
- const n=Number(v.vehicle_docs_count)||0;
- el.textContent=n>0?'('+n+')':'';
-}
-
-async function loadVehicleDocuments(){
- if(!currentVehicleId)return;
- try{
- const list=await api('garage_documents','GET',null,'&vehicle_id='+currentVehicleId+'&vehicle_only=1');
- renderGarageDocList('vehicle-documents-list',list);
- }catch(e){toast(e.message,'error');}
-}
-
-async function loadMaintenanceDocuments(){
- if(!currentMaintenanceId)return;
- try{
- const list=await api('garage_documents','GET',null,'&maintenance_id='+currentMaintenanceId);
- renderGarageDocList('maintenance-documents-list',list);
- }catch(e){toast(e.message,'error');}
-}
-
-function renderGarageDocList(elId,list){
- const el=document.getElementById(elId);
- if(!el)return;
- if(!list||!list.length){
- el.innerHTML='
Aucun document pour l’instant
';
+async function doUploadPhoto() {
+ const id = $("#upload-vehicle-id").value;
+ const file = $("#upload-photo-file").files[0];
+ if (!file) {
+ toast("Choisissez une photo", "error");
return;
}
- el.innerHTML='
'+
- list.map(d=>{
- const href=API+'?action=garage_document&id='+encodeURIComponent(d.id);
- const title=escHtml(d.label||d.original_name||d.filename||'Document');
- const meta=fmtDate(d.created_at)+' · '+formatBytes(d.size||0);
- return '- '+
- '📄 '+title+''+
- ''+meta+''+
- ''+
- '
';
- }).join('')+
- '
';
+ const fd = new FormData();
+ fd.append("photo", file);
+ try {
+ await fetch(API + "?action=upload_vehicle_photo&id=" + id, {
+ method: "POST",
+ body: fd,
+ });
+ toast("Photo mise à jour");
+ closeModal("modal-upload-photo");
+ loadVehicleDetail(id);
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-async function uploadVehicleDocument(){
- const inp=document.getElementById('vehicle-doc-file');
- if(!inp||!inp.files[0]){toast('Choisissez un fichier','error');return;}
- const fd=new FormData();
- fd.append('vehicle_id',String(currentVehicleId));
- fd.append('file',inp.files[0]);
- const lab=document.getElementById('vehicle-doc-label')?.value.trim();
- if(lab)fd.append('label',lab);
- try{
- const r=await fetch(API+'?action=garage_documents',{method:'POST',body:fd});
- const j=await r.json();
- if(!j.ok)throw new Error(j.error||'Erreur');
- toast('Document ajouté');
- inp.value='';
- const ll=document.getElementById('vehicle-doc-label');if(ll)ll.value='';
- const v=await api('vehicles','GET',null,'&id='+currentVehicleId);
+function setVehicleDocTabBadge(v) {
+ const el = document.getElementById("vehicle-docs-tab-count");
+ if (!el) return;
+ const n = Number(v.vehicle_docs_count) || 0;
+ el.textContent = n > 0 ? "(" + n + ")" : "";
+}
+
+async function loadVehicleDocuments() {
+ if (!currentVehicleId) return;
+ try {
+ const list = await api(
+ "garage_documents",
+ "GET",
+ null,
+ "&vehicle_id=" + currentVehicleId + "&vehicle_only=1",
+ );
+ renderGarageDocList("vehicle-documents-list", list);
+ } catch (e) {
+ toast(e.message, "error");
+ }
+}
+
+async function loadMaintenanceDocuments() {
+ if (!currentMaintenanceId) return;
+ try {
+ const list = await api(
+ "garage_documents",
+ "GET",
+ null,
+ "&maintenance_id=" + currentMaintenanceId,
+ );
+ renderGarageDocList("maintenance-documents-list", list);
+ } catch (e) {
+ toast(e.message, "error");
+ }
+}
+
+function renderGarageDocList(elId, list) {
+ const el = document.getElementById(elId);
+ if (!el) return;
+ if (!list || !list.length) {
+ el.innerHTML =
+ '
Aucun document pour l’instant
';
+ return;
+ }
+ el.innerHTML =
+ '
' +
+ list
+ .map((d) => {
+ const href =
+ API + "?action=garage_document&id=" + encodeURIComponent(d.id);
+ const title = escHtml(
+ d.label || d.original_name || d.filename || "Document",
+ );
+ const meta = fmtDate(d.created_at) + " · " + formatBytes(d.size || 0);
+ return (
+ '- ' +
+ '📄 ' +
+ title +
+ "" +
+ '' +
+ meta +
+ "" +
+ '' +
+ "
"
+ );
+ })
+ .join("") +
+ "
";
+}
+
+async function uploadVehicleDocument() {
+ const inp = document.getElementById("vehicle-doc-file");
+ if (!inp || !inp.files[0]) {
+ toast("Choisissez un fichier", "error");
+ return;
+ }
+ const fd = new FormData();
+ fd.append("vehicle_id", String(currentVehicleId));
+ fd.append("file", inp.files[0]);
+ const lab = document.getElementById("vehicle-doc-label")?.value.trim();
+ if (lab) fd.append("label", lab);
+ try {
+ const r = await fetch(API + "?action=garage_documents", {
+ method: "POST",
+ body: fd,
+ });
+ const j = await r.json();
+ if (!j.ok) throw new Error(j.error || "Erreur");
+ toast("Document ajouté");
+ inp.value = "";
+ const ll = document.getElementById("vehicle-doc-label");
+ if (ll) ll.value = "";
+ const v = await api("vehicles", "GET", null, "&id=" + currentVehicleId);
renderVehicleHeader(v);
setVehicleDocTabBadge(v);
await loadVehicleDocuments();
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-async function uploadMaintenanceDocument(){
- const inp=document.getElementById('maint-doc-file');
- if(!inp||!inp.files[0]){toast('Choisissez un fichier','error');return;}
- if(!currentVehicleId||!currentMaintenanceId){toast('Contexte manquant','error');return;}
- const fd=new FormData();
- fd.append('vehicle_id',String(currentVehicleId));
- fd.append('maintenance_id',String(currentMaintenanceId));
- fd.append('file',inp.files[0]);
- const lab=document.getElementById('maint-doc-label')?.value.trim();
- if(lab)fd.append('label',lab);
- try{
- const r=await fetch(API+'?action=garage_documents',{method:'POST',body:fd});
- const j=await r.json();
- if(!j.ok)throw new Error(j.error||'Erreur');
- toast('Document ajouté');
- inp.value='';
- const ll=document.getElementById('maint-doc-label');if(ll)ll.value='';
- const m=await api('maintenances','GET',null,'&id='+currentMaintenanceId);
+async function uploadMaintenanceDocument() {
+ const inp = document.getElementById("maint-doc-file");
+ if (!inp || !inp.files[0]) {
+ toast("Choisissez un fichier", "error");
+ return;
+ }
+ if (!currentVehicleId || !currentMaintenanceId) {
+ toast("Contexte manquant", "error");
+ return;
+ }
+ const fd = new FormData();
+ fd.append("vehicle_id", String(currentVehicleId));
+ fd.append("maintenance_id", String(currentMaintenanceId));
+ fd.append("file", inp.files[0]);
+ const lab = document.getElementById("maint-doc-label")?.value.trim();
+ if (lab) fd.append("label", lab);
+ try {
+ const r = await fetch(API + "?action=garage_documents", {
+ method: "POST",
+ body: fd,
+ });
+ const j = await r.json();
+ if (!j.ok) throw new Error(j.error || "Erreur");
+ toast("Document ajouté");
+ inp.value = "";
+ const ll = document.getElementById("maint-doc-label");
+ if (ll) ll.value = "";
+ const m = await api(
+ "maintenances",
+ "GET",
+ null,
+ "&id=" + currentMaintenanceId,
+ );
renderMaintenanceDetailHeader(m);
await loadMaintenanceDocuments();
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
-async function deleteGarageDocument(id){
- if(!confirm('Supprimer ce document ?'))return;
- try{
- await api('garage_documents','DELETE',null,'&id='+id);
- toast('Document supprimé');
- if(currentPage==='vehicle'){
- const v=await api('vehicles','GET',null,'&id='+currentVehicleId);
+async function deleteGarageDocument(id) {
+ if (!confirm("Supprimer ce document ?")) return;
+ try {
+ await api("garage_documents", "DELETE", null, "&id=" + id);
+ toast("Document supprimé");
+ if (currentPage === "vehicle") {
+ const v = await api("vehicles", "GET", null, "&id=" + currentVehicleId);
renderVehicleHeader(v);
setVehicleDocTabBadge(v);
await loadVehicleDocuments();
- }else if(currentPage==='maintenance'){
- const m=await api('maintenances','GET',null,'&id='+currentMaintenanceId);
+ } else if (currentPage === "maintenance") {
+ const m = await api(
+ "maintenances",
+ "GET",
+ null,
+ "&id=" + currentMaintenanceId,
+ );
renderMaintenanceDetailHeader(m);
await loadMaintenanceDocuments();
}
- }catch(e){toast(e.message,'error');}
+ } catch (e) {
+ toast(e.message, "error");
+ }
}
// ─── TABS ────────────────────────────────────────────────────────────────────
-function switchTab(tab){
- $$('.tab-btn').forEach(b=>b.classList.remove('active'));
- $$('.tab-pane').forEach(p=>p.classList.remove('active'));
- document.querySelector('.tab-btn[data-tab="'+tab+'"]')?.classList.add('active');
- document.getElementById('tab-'+tab)?.classList.add('active');
- if(tab==='documents'&¤tVehicleId)loadVehicleDocuments();
+function switchTab(tab) {
+ $$(".tab-btn").forEach((b) => b.classList.remove("active"));
+ $$(".tab-pane").forEach((p) => p.classList.remove("active"));
+ document
+ .querySelector('.tab-btn[data-tab="' + tab + '"]')
+ ?.classList.add("active");
+ document.getElementById("tab-" + tab)?.classList.add("active");
+ if (tab === "documents" && currentVehicleId) loadVehicleDocuments();
}
// ─── PHOTO PREVIEW ────────────────────────────────────────────────────────────
-function previewPhoto(inputId,previewId){
- const file=document.getElementById(inputId).files[0];if(!file)return;
- const reader=new FileReader();
- reader.onload=e=>{const img=document.getElementById(previewId);img.src=e.target.result;img.style.display='block';};
+function previewPhoto(inputId, previewId) {
+ const file = document.getElementById(inputId).files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const img = document.getElementById(previewId);
+ img.src = e.target.result;
+ img.style.display = "block";
+ };
reader.readAsDataURL(file);
}
// ─── INIT ────────────────────────────────────────────────────────────────────
-document.addEventListener('DOMContentLoaded',()=>{
- $$('.nav-link').forEach(n=>n.addEventListener('click',()=>navigate(n.dataset.page)));
- $$('.modal-backdrop').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('show');}));
- navigate('dashboard');
+document.addEventListener("DOMContentLoaded", () => {
+ $$(".nav-link").forEach((n) =>
+ n.addEventListener("click", () => navigate(n.dataset.page)),
+ );
+ $$(".modal-backdrop").forEach((m) =>
+ m.addEventListener("click", (e) => {
+ if (e.target === m) m.classList.remove("show");
+ }),
+ );
+ navigate("dashboard");
});