feat: GarageManager v1.0 - Gestion entretiens vehicules

- CRUD vehicules (nom, marque, modele, annee, immat, VIN, carburant, couleur, km, photo)
- CRUD entretiens (type, date, km, cout, mecanicien, garage, rappels km/date)
- CRUD pieces (marque, reference, prix, quantite, photo, fournisseur, categorie)
- Dashboard avec stats globales et rappels entretiens
- Vue detail par vehicule (tabs Entretiens/Pieces)
- Vue globale toutes pieces
- Upload photos vehicules et pieces
- Dark theme Tailwind-inspired
- SQLite via Docker PHP 8.3 Apache
- Traefik : garage.nas.percolouco.com
This commit is contained in:
Luca
2026-03-14 23:40:35 +01:00
commit ec81babb8f
6 changed files with 1208 additions and 0 deletions
+270
View File
@@ -0,0 +1,270 @@
<?php
if(isset($_GET['file'])&&preg_match('/^[a-f0-9.]+$/',$_GET['file'])){
$path=__DIR__.'/uploads/'.$_GET['file'];
if(file_exists($path)){header('Content-Type: image/'.pathinfo($path,PATHINFO_EXTENSION));readfile($path);exit;}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GarageManager</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<nav class="navbar">
<div class="navbar-brand">&#x1F527; <span>Garage</span>Manager</div>
<div class="nav-links">
<button class="nav-link active" data-page="dashboard">&#x1F4CA; Dashboard</button>
<button class="nav-link" data-page="vehicles">&#x1F697; V&eacute;hicules</button>
<button class="nav-link" data-page="parts">&#x1F529; Pi&egrave;ces</button>
</div>
</nav>
<div id="toasts" class="toast-container"></div>
<div id="page-dashboard" class="page active">
<div class="container">
<div class="stats-grid">
<div class="stat-pill blue"><div class="label">V&eacute;hicules</div><div class="value" id="stat-vehicles">--</div></div>
<div class="stat-pill green"><div class="label">Entretiens</div><div class="value" id="stat-maintenances">--</div></div>
<div class="stat-pill amber"><div class="label">Pi&egrave;ces</div><div class="value" id="stat-parts">--</div></div>
<div class="stat-pill red"><div class="label">Co&ucirc;t total</div><div class="value" id="stat-cost">--</div></div>
</div>
<div style="display:grid;grid-template-columns:1fr 380px;gap:1.5rem;align-items:start">
<div>
<div class="card-header" style="margin-bottom:.75rem">
<h2 style="font-size:1rem;font-weight:600">Mes v&eacute;hicules</h2>
<button class="btn btn-primary btn-sm" onclick="openAddVehicle()">+ Ajouter</button>
</div>
<div id="dashboard-vehicles" class="vehicles-grid"></div>
</div>
<div class="card">
<div class="card-header"><div class="card-title">&#x1F514; Rappels &amp; prochains entretiens</div></div>
<div id="reminders-list"></div>
</div>
</div>
</div>
</div>
<div id="page-vehicles" class="page">
<div class="container">
<div class="card-header" style="margin-bottom:1rem">
<h1 style="font-size:1.2rem;font-weight:700">&#x1F697; Mes v&eacute;hicules</h1>
<button class="btn btn-primary" onclick="openAddVehicle()">+ Ajouter un v&eacute;hicule</button>
</div>
<div id="vehicles-grid" class="vehicles-grid"></div>
</div>
</div>
<div id="page-vehicle" class="page">
<div class="container">
<div style="margin-bottom:1rem">
<button class="btn btn-secondary btn-sm" onclick="navigate('vehicles')">&larr; Retour</button>
</div>
<div id="vehicle-header"></div>
<div class="tabs">
<button class="tab-btn active" data-tab="maintenances" onclick="switchTab('maintenances')">&#x1F527; Entretiens <span id="maintenance-total" style="color:var(--muted);font-size:.78rem"></span></button>
<button class="tab-btn" data-tab="parts" onclick="switchTab('parts')">&#x1F529; Pi&egrave;ces <span id="parts-total-vehicle" style="color:var(--muted);font-size:.78rem"></span></button>
</div>
<div id="tab-maintenances" class="tab-pane active">
<div style="margin-bottom:1rem;display:flex;justify-content:flex-end">
<button class="btn btn-primary btn-sm" onclick="openAddMaintenance()">+ Ajouter un entretien</button>
</div>
<div id="maintenance-list"></div>
</div>
<div id="tab-parts" class="tab-pane">
<div style="margin-bottom:1rem;display:flex;justify-content:flex-end">
<button class="btn btn-primary btn-sm" onclick="openAddPart()">+ Ajouter une pi&egrave;ce</button>
</div>
<div id="parts-list-vehicle"></div>
</div>
</div>
</div>
<div id="page-parts" class="page">
<div class="container">
<div class="card-header" style="margin-bottom:1rem">
<div>
<h1 style="font-size:1.2rem;font-weight:700">&#x1F529; Toutes les pi&egrave;ces</h1>
<p style="color:var(--muted);font-size:.85rem"><span id="all-parts-count">0</span> pi&egrave;ces &middot; Total : <span id="all-parts-total">--</span></p>
</div>
<button class="btn btn-primary" onclick="openAddPart()">+ Ajouter une pi&egrave;ce</button>
</div>
<div id="all-parts-list"></div>
</div>
</div>
<!-- MODAL: VEHICLE -->
<div id="modal-vehicle" class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modal-vehicle-title">Ajouter un v&eacute;hicule</div>
<button class="modal-close" onclick="closeModal('modal-vehicle')">&#x2715;</button>
</div>
<div class="modal-body">
<form id="form-vehicle" onsubmit="return false">
<input type="hidden" id="form-vehicle-id" name="id">
<div class="form-row">
<div class="form-group"><label class="form-label">Nom du v&eacute;hicule *</label><input class="form-control" id="vehicle-name" name="name" required placeholder="Ma Clio"></div>
<div class="form-group"><label class="form-label">Marque *</label><input class="form-control" id="vehicle-brand" name="brand" required placeholder="Renault"></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Mod&egrave;le *</label><input class="form-control" id="vehicle-model" name="model" required placeholder="Clio 4"></div>
<div class="form-group"><label class="form-label">Ann&eacute;e</label><input class="form-control" id="vehicle-year" name="year" type="number" placeholder="2019"></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Immatriculation</label><input class="form-control" id="vehicle-license-plate" name="license_plate" placeholder="AB-123-CD"></div>
<div class="form-group"><label class="form-label">Carburant</label>
<select class="form-control" id="vehicle-fuel-type" name="fuel_type">
<option>Essence</option><option>Diesel</option><option>Hybride</option><option>Electrique</option><option>GPL</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Kilom&eacute;trage actuel</label><input class="form-control" id="vehicle-current-km" name="current_km" type="number" placeholder="85000"></div>
<div class="form-group"><label class="form-label">Couleur</label><input class="form-control" id="vehicle-color" name="color" placeholder="Blanc nacr&eacute;"></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Date d'achat</label><input class="form-control" id="vehicle-purchase-date" name="purchase_date" type="date"></div>
<div class="form-group"><label class="form-label">Prix d'achat (&euro;)</label><input class="form-control" id="vehicle-purchase-price" name="purchase_price" type="number" step="0.01"></div>
</div>
<div class="form-group"><label class="form-label">VIN / Num&eacute;ro de s&eacute;rie</label><input class="form-control" id="vehicle-vin" name="vin" placeholder="VF1..."></div>
<div class="form-group">
<label class="form-label">Photo</label>
<input class="form-control" id="vehicle-photo" name="photo" type="file" accept="image/*" onchange="previewPhoto('vehicle-photo','vehicle-photo-preview')">
<img id="vehicle-photo-preview" style="display:none;max-height:120px;margin-top:.5rem;border-radius:6px" alt="">
</div>
<div class="form-group"><label class="form-label">Notes</label><textarea class="form-control" id="vehicle-notes" name="notes" rows="2"></textarea></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('modal-vehicle')">Annuler</button>
<button class="btn btn-primary" onclick="saveVehicle()">Enregistrer</button>
</div>
</div>
</div>
<!-- MODAL: MAINTENANCE -->
<div id="modal-maintenance" class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modal-maintenance-title">Ajouter un entretien</div>
<button class="modal-close" onclick="closeModal('modal-maintenance')">&#x2715;</button>
</div>
<div class="modal-body">
<form id="form-maintenance" onsubmit="return false">
<input type="hidden" id="form-maintenance-id" name="id">
<input type="hidden" id="maintenance-vehicle-id" name="vehicle_id">
<div class="form-row">
<div class="form-group"><label class="form-label">Type d'entretien *</label>
<select class="form-control" id="maintenance-type" name="type">
<option>Vidange</option><option>R&eacute;vision</option><option>Freins</option><option>Pneus</option>
<option>Distribution</option><option>Filtres</option><option>Batterie</option><option>Climatisation</option>
<option>Carrosserie</option><option>Diagnostic</option><option>Contr&ocirc;le technique</option><option>Autre</option>
</select>
</div>
<div class="form-group"><label class="form-label">Date *</label><input class="form-control" id="maintenance-date" name="date" type="date" required></div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Kilom&eacute;trage au moment</label><input class="form-control" id="maintenance-km" name="km" type="number" placeholder="85000"></div>
<div class="form-group"><label class="form-label">Co&ucirc;t main d'oeuvre (&euro;)</label><input class="form-control" id="maintenance-cost" name="cost" type="number" step="0.01" placeholder="0"></div>
</div>
<div class="form-group"><label class="form-label">Description</label><textarea class="form-control" id="maintenance-description" name="description" rows="2" placeholder="D&eacute;tails de l'entretien..."></textarea></div>
<div class="form-row">
<div class="form-group"><label class="form-label">M&eacute;canicien / Garagiste</label><input class="form-control" id="maintenance-mechanic" name="mechanic" placeholder="Nom"></div>
<div class="form-group"><label class="form-label">Garage / Atelier</label><input class="form-control" id="maintenance-garage" name="garage" placeholder="Garage XYZ"></div>
</div>
<div style="background:rgba(37,99,235,.05);border:1px solid rgba(37,99,235,.1);border-radius:8px;padding:1rem;margin-top:.5rem">
<div style="font-size:.82rem;color:var(--muted);margin-bottom:.75rem;font-weight:600">&#x1F514; Prochain entretien (rappel)</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Date pr&eacute;vue</label><input class="form-control" id="maintenance-next-date" name="next_date" type="date"></div>
<div class="form-group"><label class="form-label">Kilom&eacute;trage pr&eacute;vu</label><input class="form-control" id="maintenance-next-km" name="next_km" type="number" placeholder="95000"></div>
</div>
</div>
<div class="form-group" style="margin-top:1rem"><label class="form-label">Notes</label><textarea class="form-control" id="maintenance-notes" name="notes" rows="2"></textarea></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('modal-maintenance')">Annuler</button>
<button class="btn btn-primary" onclick="saveMaintenance()">Enregistrer</button>
</div>
</div>
</div>
<!-- MODAL: PART -->
<div id="modal-part" class="modal-backdrop">
<div class="modal">
<div class="modal-header">
<div class="modal-title" id="modal-part-title">Ajouter une pi&egrave;ce</div>
<button class="modal-close" onclick="closeModal('modal-part')">&#x2715;</button>
</div>
<div class="modal-body">
<form id="form-part" onsubmit="return false">
<input type="hidden" id="form-part-id" name="id">
<input type="hidden" id="part-vehicle-id" name="vehicle_id">
<div class="form-row">
<div class="form-group"><label class="form-label">Nom de la pi&egrave;ce *</label><input class="form-control" id="part-name" name="name" required placeholder="Filtre &agrave; huile"></div>
<div class="form-group"><label class="form-label">Cat&eacute;gorie</label>
<select class="form-control" id="part-category" name="category">
<option>Moteur</option><option>Freinage</option><option>Suspension</option><option>Transmission</option>
<option>Carrosserie</option><option>Electricite</option><option>Eclairage</option><option>Filtration</option>
<option>Refroidissement</option><option>Echappement</option><option>Pneumatiques</option><option>Autre</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Marque</label><input class="form-control" id="part-brand" name="brand" placeholder="Bosch, NGK..."></div>
<div class="form-group"><label class="form-label">R&eacute;f&eacute;rence</label><input class="form-control" id="part-reference" name="reference" placeholder="0451103366"></div>
</div>
<div class="form-row-3">
<div class="form-group"><label class="form-label">Prix unitaire (&euro;)</label><input class="form-control" id="part-price" name="price" type="number" step="0.01" placeholder="12.50"></div>
<div class="form-group"><label class="form-label">Quantit&eacute;</label><input class="form-control" id="part-quantity" name="quantity" type="number" value="1" min="1"></div>
<div class="form-group"><label class="form-label">Unit&eacute;</label>
<select class="form-control" id="part-unit" name="unit">
<option value="piece">pi&egrave;ce</option><option value="litre">litre</option><option value="kg">kg</option><option value="m">m&egrave;tre</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group"><label class="form-label">Fournisseur</label><input class="form-control" id="part-supplier" name="supplier" placeholder="Amazon, Oscaro..."></div>
<div class="form-group"><label class="form-label">Date d'achat</label><input class="form-control" id="part-purchase-date" name="purchase_date" type="date"></div>
</div>
<div class="form-group">
<label class="form-label">Photo de la pi&egrave;ce</label>
<input class="form-control" id="part-photo" name="photo" type="file" accept="image/*" onchange="previewPhoto('part-photo','part-photo-preview')">
<img id="part-photo-preview" style="display:none;max-height:100px;margin-top:.5rem;border-radius:6px" alt="">
</div>
<div class="form-group"><label class="form-label">Notes</label><textarea class="form-control" id="part-notes" name="notes" rows="2"></textarea></div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('modal-part')">Annuler</button>
<button class="btn btn-primary" onclick="savePart()">Enregistrer</button>
</div>
</div>
</div>
<!-- MODAL: UPLOAD PHOTO -->
<div id="modal-upload-photo" class="modal-backdrop">
<div class="modal" style="max-width:400px">
<div class="modal-header">
<div class="modal-title">&#x1F4F7; Changer la photo</div>
<button class="modal-close" onclick="closeModal('modal-upload-photo')">&#x2715;</button>
</div>
<div class="modal-body">
<input type="hidden" id="upload-vehicle-id">
<div class="form-group"><label class="form-label">Nouvelle photo</label><input class="form-control" id="upload-photo-file" type="file" accept="image/*"></div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('modal-upload-photo')">Annuler</button>
<button class="btn btn-primary" onclick="doUploadPhoto()">Mettre &agrave; jour</button>
</div>
</div>
</div>
<script src="assets/app.js"></script>
</body>
</html>