feat: redesign dark futuriste (Luca) — thème cyan/volt, sidebar dark, JetBrains Mono + README
This commit is contained in:
+610
-18
@@ -4,31 +4,623 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}GarageManager{% endblock %}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg-primary: #0a0f1a;
|
||||
--bg-secondary: #111827;
|
||||
--bg-card: #1a2234;
|
||||
--bg-input: #0d1321;
|
||||
--accent-electric: #00d4ff;
|
||||
--accent-volt: #facc15;
|
||||
--accent-green: #22c55e;
|
||||
--accent-red: #ef4444;
|
||||
--text-primary: #f1f5f9;
|
||||
--text-secondary: #94a3b8;
|
||||
--text-muted: #64748b;
|
||||
--border: #2d3a52;
|
||||
--shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
||||
--glow-electric: 0 0 20px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Outfit", sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, rgba(0,212,255,0.08) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(250,204,21,0.06) 0%, transparent 50%),
|
||||
linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* ── Sidebar ── */
|
||||
.app-sidebar {
|
||||
width: 220px;
|
||||
background: var(--bg-card);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 0; left: 0; bottom: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
padding: 1.25rem 1.25rem 1rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, var(--accent-electric), var(--accent-volt));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar-brand i {
|
||||
background: linear-gradient(135deg, var(--accent-electric), var(--accent-volt));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
flex: 1;
|
||||
padding: 0.75rem 0.5rem;
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
font-size: 0.65rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
padding: 0.75rem 0.75rem 0.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
margin: 0.1rem 0;
|
||||
transition: all 0.2s;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sidebar-link i {
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sidebar-link:hover {
|
||||
background: rgba(0, 212, 255, 0.08);
|
||||
color: var(--accent-electric);
|
||||
}
|
||||
|
||||
.sidebar-link.active {
|
||||
background: rgba(0, 212, 255, 0.12);
|
||||
color: var(--accent-electric);
|
||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 0.75rem 1rem;
|
||||
border-top: 1px solid var(--border);
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Main content ── */
|
||||
.app-main {
|
||||
margin-left: 220px;
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── Cards ── */
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: rgba(0, 212, 255, 0.3);
|
||||
box-shadow: var(--glow-electric);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ── Tables ── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.875rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tr:hover td {
|
||||
background: rgba(0, 212, 255, 0.03);
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
/* ── Buttons ── */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent-electric), #0099cc);
|
||||
color: #0a0f1a;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
font-family: "Outfit", sans-serif;
|
||||
transition: box-shadow 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
box-shadow: var(--glow-electric);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
font-family: "Outfit", sans-serif;
|
||||
transition: border-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--accent-electric);
|
||||
color: var(--accent-electric);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
color: var(--accent-red);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
font-family: "Outfit", sans-serif;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.35rem 0.875rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
/* ── Inputs ── */
|
||||
input, select, textarea {
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-family: "Outfit", sans-serif;
|
||||
font-size: 0.875rem;
|
||||
width: 100%;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
border-color: var(--accent-electric);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1);
|
||||
}
|
||||
|
||||
input::placeholder, textarea::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
select option {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* ── Badges ── */
|
||||
.badge {
|
||||
padding: 0.2rem 0.65rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: var(--accent-green);
|
||||
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: rgba(250, 204, 21, 0.12);
|
||||
color: var(--accent-volt);
|
||||
border: 1px solid rgba(250, 204, 21, 0.2);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
color: var(--accent-red);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: rgba(0, 212, 255, 0.1);
|
||||
color: var(--accent-electric);
|
||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
|
||||
.badge-neutral {
|
||||
background: rgba(100, 116, 139, 0.12);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ── Stats cards ── */
|
||||
.stat-value {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-electric);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* ── Mono ── */
|
||||
.mono {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
}
|
||||
|
||||
/* ── Page header ── */
|
||||
.page-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* ── Back link ── */
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--accent-electric);
|
||||
}
|
||||
|
||||
/* ── Grid ── */
|
||||
.grid { display: grid; }
|
||||
.grid-2 { grid-template-columns: repeat(2, 1fr); gap: 1rem; }
|
||||
.grid-3 { grid-template-columns: repeat(3, 1fr); gap: 1rem; }
|
||||
.grid-4 { grid-template-columns: repeat(4, 1fr); gap: 1rem; }
|
||||
|
||||
/* ── Flex utils ── */
|
||||
.flex { display: flex; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.items-center { align-items: center; }
|
||||
.items-start { align-items: flex-start; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.gap-1 { gap: 0.25rem; }
|
||||
.gap-2 { gap: 0.5rem; }
|
||||
.gap-3 { gap: 0.75rem; }
|
||||
.gap-4 { gap: 1rem; }
|
||||
.flex-1 { flex: 1; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
|
||||
/* ── Spacing ── */
|
||||
.mb-1 { margin-bottom: 0.25rem; }
|
||||
.mb-2 { margin-bottom: 0.5rem; }
|
||||
.mb-3 { margin-bottom: 0.75rem; }
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mb-6 { margin-bottom: 1.5rem; }
|
||||
.mb-8 { margin-bottom: 2rem; }
|
||||
.mt-1 { margin-top: 0.25rem; }
|
||||
.mt-2 { margin-top: 0.5rem; }
|
||||
.mt-4 { margin-top: 1rem; }
|
||||
.ml-2 { margin-left: 0.5rem; }
|
||||
|
||||
/* ── Text utils ── */
|
||||
.text-electric { color: var(--accent-electric); }
|
||||
.text-volt { color: var(--accent-volt); }
|
||||
.text-green { color: var(--accent-green); }
|
||||
.text-red { color: var(--accent-red); }
|
||||
.text-muted { color: var(--text-muted); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-xs { font-size: 0.75rem; }
|
||||
.text-sm { font-size: 0.875rem; }
|
||||
.text-right { text-align: right; }
|
||||
.text-center { text-align: center; }
|
||||
.font-bold { font-weight: 700; }
|
||||
.font-semibold { font-weight: 600; }
|
||||
.font-medium { font-weight: 500; }
|
||||
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.uppercase { text-transform: uppercase; }
|
||||
|
||||
/* ── Empty state ── */
|
||||
.empty-state {
|
||||
padding: 3rem;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: block;
|
||||
color: var(--border);
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* ── Overflow ── */
|
||||
.overflow-x-auto { overflow-x: auto; }
|
||||
|
||||
/* ── Link actions ── */
|
||||
.link-action {
|
||||
color: var(--accent-electric);
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.link-action:hover { opacity: 0.7; }
|
||||
|
||||
.link-danger {
|
||||
color: var(--accent-red);
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-family: "Outfit", sans-serif;
|
||||
padding: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.link-danger:hover { opacity: 0.7; }
|
||||
|
||||
.link-edit {
|
||||
color: var(--accent-volt);
|
||||
text-decoration: none;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.link-edit:hover { opacity: 0.7; }
|
||||
|
||||
/* ── Section dividers ── */
|
||||
.section-divider {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
/* ── Scrollable list ── */
|
||||
.scroll-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem;
|
||||
background: var(--bg-input);
|
||||
}
|
||||
|
||||
.scroll-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.scroll-list-item:hover {
|
||||
background: rgba(0, 212, 255, 0.06);
|
||||
}
|
||||
|
||||
/* ── Toggle section ── */
|
||||
.toggle-section {
|
||||
display: none;
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.toggle-section.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ── Collapsible ── */
|
||||
details summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
details summary::-webkit-details-marker { display: none; }
|
||||
|
||||
/* ── Photo thumb ── */
|
||||
.photo-thumb {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.photo-placeholder {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--border);
|
||||
}
|
||||
</style>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-gray-100 min-h-screen flex">
|
||||
<div class="w-64 bg-slate-800 min-h-screen flex flex-col fixed left-0 top-0">
|
||||
<div class="p-6 border-b border-slate-700">
|
||||
<h1 class="text-white text-xl font-bold flex items-center gap-2">
|
||||
<i class="fas fa-wrench text-orange-400"></i> GarageManager
|
||||
</h1>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<aside class="app-sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<i class="fas fa-wrench"></i> GarageManager
|
||||
</div>
|
||||
<nav class="flex-1 p-4 space-y-1">
|
||||
<a href="/" class="flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition {% if page == 'dashboard' %}bg-orange-500 text-white{% else %}text-slate-300 hover:bg-slate-700 hover:text-white{% endif %}">
|
||||
<i class="fas fa-chart-line w-5"></i> Dashboard
|
||||
<nav class="sidebar-nav">
|
||||
<div class="sidebar-section">Navigation</div>
|
||||
<a href="/" class="sidebar-link {% if page == 'dashboard' %}active{% endif %}">
|
||||
<i class="fas fa-chart-line"></i> Dashboard
|
||||
</a>
|
||||
<a href="/vehicules" class="flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition {% if page == 'vehicules' %}bg-orange-500 text-white{% else %}text-slate-300 hover:bg-slate-700 hover:text-white{% endif %}">
|
||||
<i class="fas fa-car w-5"></i> Véhicules
|
||||
<a href="/vehicules" class="sidebar-link {% if page == 'vehicules' %}active{% endif %}">
|
||||
<i class="fas fa-car"></i> Véhicules
|
||||
</a>
|
||||
<a href="/interventions" class="flex items-center gap-3 px-4 py-3 rounded-lg text-sm font-medium transition {% if page == 'interventions' %}bg-orange-500 text-white{% else %}text-slate-300 hover:bg-slate-700 hover:text-white{% endif %}">
|
||||
<i class="fas fa-tools w-5"></i> Interventions
|
||||
<a href="/interventions" class="sidebar-link {% if page == 'interventions' %}active{% endif %}">
|
||||
<i class="fas fa-tools"></i> Interventions
|
||||
</a>
|
||||
<a href="/clients" class="sidebar-link {% if page == 'clients' %}active{% endif %}">
|
||||
<i class="fas fa-users"></i> Clients
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-slate-700 text-xs text-slate-500 text-center">GarageManager v2.1</div>
|
||||
</div>
|
||||
<div class="ml-64 flex-1 p-8">
|
||||
<div class="sidebar-footer">GarageManager v2.1</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="app-main">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,49 +1,74 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ client.nom }} {{ client.prenom }} - GarageManager{% endblock %}
|
||||
{% block title %}{{ client.nom }} {{ client.prenom }} — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-6">
|
||||
<a href="/clients" class="text-slate-500 hover:text-slate-700 text-sm flex items-center gap-1 mb-4"><i class="fas fa-arrow-left"></i> Retour aux clients</a>
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800">{{ client.nom }} {{ client.prenom }}</h2>
|
||||
<div class="flex flex-wrap gap-4 mt-2 text-sm text-slate-500">
|
||||
{% if client.telephone %}<span><i class="fas fa-phone mr-1"></i>{{ client.telephone }}</span>{% endif %}
|
||||
{% if client.email %}<span><i class="fas fa-envelope mr-1"></i>{{ client.email }}</span>{% endif %}
|
||||
{% if client.adresse %}<span><i class="fas fa-map-marker-alt mr-1"></i>{{ client.adresse }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/clients/{{ client.id }}/edit" class="border border-orange-300 text-orange-500 px-4 py-2 rounded-lg text-sm hover:bg-orange-50">Modifier</a>
|
||||
<form method="post" action="/clients/{{ client.id }}/delete" onsubmit="return confirm('Supprimer ce client et tous ses véhicules ?')">
|
||||
<button class="border border-red-300 text-red-500 px-4 py-2 rounded-lg text-sm hover:bg-red-50">Supprimer</button>
|
||||
</form>
|
||||
|
||||
<a href="/clients" class="back-link"><i class="fas fa-arrow-left"></i> Retour aux clients</a>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-start mb-8">
|
||||
<div>
|
||||
<h1 class="page-title">{{ client.nom }} {{ client.prenom }}</h1>
|
||||
<div class="flex gap-4 mt-2" style="flex-wrap:wrap">
|
||||
{% if client.telephone %}
|
||||
<span class="text-sm text-secondary"><i class="fas fa-phone mr-1 text-electric"></i>{{ client.telephone }}</span>
|
||||
{% endif %}
|
||||
{% if client.email %}
|
||||
<span class="text-sm text-secondary"><i class="fas fa-envelope mr-1 text-electric"></i>{{ client.email }}</span>
|
||||
{% endif %}
|
||||
{% if client.adresse %}
|
||||
<span class="text-sm text-secondary"><i class="fas fa-map-marker-alt mr-1 text-electric"></i>{{ client.adresse }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/clients/{{ client.id }}/edit" class="btn-secondary btn-sm">
|
||||
<i class="fas fa-edit"></i> Modifier
|
||||
</a>
|
||||
<form method="post" action="/clients/{{ client.id }}/delete" onsubmit="return confirm('Supprimer ce client et tous ses véhicules ?')">
|
||||
<button class="btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i> Supprimer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow">
|
||||
<div class="p-6 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-slate-800">Véhicules ({{ vehicules|length }})</h3>
|
||||
<a href="/vehicules/new" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600">+ Ajouter un véhicule</a>
|
||||
|
||||
<!-- Véhicules du client -->
|
||||
<div class="card" style="padding:0">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<i class="fas fa-car text-electric" style="margin-right:0.4rem"></i>
|
||||
Véhicules
|
||||
<span class="badge badge-info ml-2">{{ vehicules|length }}</span>
|
||||
</span>
|
||||
<a href="/vehicules/new" class="btn-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> Ajouter
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if vehicules %}
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div>
|
||||
{% for v in vehicules %}
|
||||
<div class="px-6 py-4 flex justify-between items-center hover:bg-gray-50">
|
||||
<div style="padding:1rem 1.5rem;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;transition:background 0.15s" onmouseover="this.style.background='rgba(0,212,255,0.03)'" onmouseout="this.style.background=''">
|
||||
<div>
|
||||
<span class="font-medium text-slate-800">{{ v.immatriculation }}</span>
|
||||
<span class="text-slate-500 ml-2">{{ v.marque }} {{ v.modele }}</span>
|
||||
{% if v.annee %}<span class="text-slate-400 text-sm ml-2">({{ v.annee }})</span>{% endif %}
|
||||
{% if v.kilometrage %}<span class="text-slate-400 text-sm ml-2">— {{ v.kilometrage }} km</span>{% endif %}
|
||||
<span class="font-semibold mono" style="color:var(--accent-electric)">{{ v.immatriculation }}</span>
|
||||
<span class="text-secondary ml-2">{{ v.marque }} {{ v.modele }}</span>
|
||||
{% if v.annee %}<span class="text-muted text-xs ml-2">({{ v.annee }})</span>{% endif %}
|
||||
{% if v.kilometrage %}<span class="text-muted text-xs ml-2 mono">— {{ v.kilometrage }} km</span>{% endif %}
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs">{{ v.nb_interventions }} intervention(s)</span>
|
||||
<a href="/vehicules/{{ v.id }}" class="text-blue-500 hover:underline text-sm">Voir →</a>
|
||||
<span class="badge badge-info">{{ v.nb_interventions }} intervention(s)</span>
|
||||
<a href="/vehicules/{{ v.id }}" class="link-action text-sm">Voir →</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-8 text-center text-slate-400 text-sm">Aucun véhicule enregistré pour ce client.</div>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-car"></i>
|
||||
<p>Aucun véhicule enregistré pour ce client</p>
|
||||
<a href="/vehicules/new" class="link-action">Ajouter un véhicule →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
+34
-20
@@ -1,38 +1,52 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{% if client %}Modifier{% else %}Nouveau{% endif %} client - GarageManager{% endblock %}
|
||||
{% block title %}{% if client %}Modifier{% else %}Nouveau{% endif %} client — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-8">
|
||||
<a href="/clients" class="text-slate-500 hover:text-slate-700 text-sm flex items-center gap-1 mb-4"><i class="fas fa-arrow-left"></i> Retour</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800">{% if client %}Modifier le client{% else %}Nouveau client{% endif %}</h2>
|
||||
|
||||
<a href="/clients" class="back-link"><i class="fas fa-arrow-left"></i> Retour aux clients</a>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">
|
||||
<i class="fas fa-{% if client %}edit{% else %}user-plus{% endif %} text-electric" style="margin-right:0.5rem"></i>
|
||||
{% if client %}Modifier le client{% else %}Nouveau client{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-8 max-w-2xl">
|
||||
<form method="post" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
<div class="card" style="max-width:580px">
|
||||
<form method="post" style="display:flex;flex-direction:column;gap:1.25rem">
|
||||
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Nom *</label>
|
||||
<input name="nom" value="{{ client.nom if client else '' }}" required class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Nom *</label>
|
||||
<input name="nom" value="{{ client.nom if client else '' }}" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Prénom *</label>
|
||||
<input name="prenom" value="{{ client.prenom if client else '' }}" required class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Prénom *</label>
|
||||
<input name="prenom" value="{{ client.prenom if client else '' }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Téléphone</label>
|
||||
<input name="telephone" value="{{ client.telephone if client else '' }}" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Téléphone</label>
|
||||
<input name="telephone" value="{{ client.telephone if client else '' }}" placeholder="06 xx xx xx xx">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Email</label>
|
||||
<input name="email" type="email" value="{{ client.email if client else '' }}" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Email</label>
|
||||
<input name="email" type="email" value="{{ client.email if client else '' }}" placeholder="contact@exemple.fr">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Adresse</label>
|
||||
<textarea name="adresse" rows="2" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">{{ client.adresse if client else '' }}</textarea>
|
||||
<label>Adresse</label>
|
||||
<textarea name="adresse" rows="2" placeholder="Adresse complète…">{{ client.adresse if client else '' }}</textarea>
|
||||
</div>
|
||||
<div class="flex gap-3 pt-4">
|
||||
<button type="submit" class="bg-orange-500 text-white px-6 py-2 rounded-lg hover:bg-orange-600 transition text-sm font-medium">Enregistrer</button>
|
||||
<a href="/clients" class="text-slate-500 px-6 py-2 rounded-lg border border-gray-200 hover:bg-gray-50 text-sm">Annuler</a>
|
||||
|
||||
<div style="display:flex;gap:0.75rem;padding-top:0.5rem;border-top:1px solid var(--border)">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save"></i> Enregistrer
|
||||
</button>
|
||||
<a href="/clients" class="btn-secondary">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
+55
-34
@@ -1,54 +1,75 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Clients - GarageManager{% endblock %}
|
||||
{% block title %}Clients — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div><h2 class="text-2xl font-bold text-slate-800">Clients</h2><p class="text-slate-500 mt-1">{{ clients|length }} client(s)</p></div>
|
||||
<a href="/clients/new" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600 transition flex items-center gap-2"><i class="fas fa-plus"></i> Nouveau client</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow">
|
||||
<div class="p-4 border-b border-gray-100">
|
||||
<form method="get" class="flex gap-2">
|
||||
<input name="q" value="{{ q }}" placeholder="Rechercher par nom, téléphone..." class="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<button type="submit" class="bg-slate-700 text-white px-4 py-2 rounded-lg text-sm hover:bg-slate-800">Rechercher</button>
|
||||
{% if q %}<a href="/clients" class="text-slate-500 px-3 py-2 text-sm hover:text-slate-700 flex items-center">✕</a>{% endif %}
|
||||
</form>
|
||||
|
||||
<div class="page-header flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="page-title"><i class="fas fa-users text-electric" style="margin-right:0.5rem"></i>Clients</h1>
|
||||
<p class="page-subtitle">{{ clients|length }} client(s) enregistré(s)</p>
|
||||
</div>
|
||||
<a href="/clients/new" class="btn-primary">
|
||||
<i class="fas fa-plus"></i> Nouveau client
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="card mb-4" style="padding:1rem 1.25rem">
|
||||
<form method="get" class="flex gap-2">
|
||||
<input name="q" value="{{ q }}" placeholder="Rechercher par nom, téléphone…" style="flex:1">
|
||||
<button type="submit" class="btn-secondary" style="width:auto;white-space:nowrap">
|
||||
<i class="fas fa-search"></i> Rechercher
|
||||
</button>
|
||||
{% if q %}
|
||||
<a href="/clients" class="btn-secondary" style="width:auto">✕</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:0">
|
||||
<div class="overflow-x-auto">
|
||||
{% if clients %}
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-slate-600 text-xs uppercase">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left">Nom</th>
|
||||
<th class="px-6 py-3 text-left">Téléphone</th>
|
||||
<th class="px-6 py-3 text-left">Email</th>
|
||||
<th class="px-6 py-3 text-left">Véhicules</th>
|
||||
<th class="px-6 py-3 text-left">Actions</th>
|
||||
<th>Nom</th>
|
||||
<th>Téléphone</th>
|
||||
<th>Email</th>
|
||||
<th>Véhicules</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<tbody>
|
||||
{% for c in clients %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4"><a href="/clients/{{ c.id }}" class="font-medium text-slate-800 hover:text-orange-500">{{ c.nom }} {{ c.prenom }}</a></td>
|
||||
<td class="px-6 py-4 text-slate-600">{{ c.telephone or '-' }}</td>
|
||||
<td class="px-6 py-4 text-slate-600">{{ c.email or '-' }}</td>
|
||||
<td class="px-6 py-4"><span class="bg-purple-100 text-purple-700 px-2 py-1 rounded-full text-xs">{{ c.nb_vehicules }}</span></td>
|
||||
<td class="px-6 py-4 flex gap-3">
|
||||
<a href="/clients/{{ c.id }}" class="text-blue-500 hover:underline">Voir</a>
|
||||
<a href="/clients/{{ c.id }}/edit" class="text-orange-500 hover:underline">Modifier</a>
|
||||
<form method="post" action="/clients/{{ c.id }}/delete" class="inline" onsubmit="return confirm('Supprimer ce client ?')">
|
||||
<button type="submit" class="text-red-500 hover:underline">Supprimer</button>
|
||||
</form>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/clients/{{ c.id }}" class="font-semibold" style="text-decoration:none;color:var(--text-primary)">{{ c.nom }} {{ c.prenom }}</a>
|
||||
</td>
|
||||
<td class="text-secondary text-sm">{{ c.telephone or '—' }}</td>
|
||||
<td class="text-secondary text-sm">{{ c.email or '—' }}</td>
|
||||
<td>
|
||||
<span class="badge badge-info">{{ c.nb_vehicules }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-3">
|
||||
<a href="/clients/{{ c.id }}" class="link-action">Voir</a>
|
||||
<a href="/clients/{{ c.id }}/edit" class="link-edit">Modifier</a>
|
||||
<form method="post" action="/clients/{{ c.id }}/delete" style="display:inline" onsubmit="return confirm('Supprimer ce client ?')">
|
||||
<button class="link-danger">Supprimer</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-8 text-center text-slate-400">
|
||||
<i class="fas fa-users text-3xl mb-3"></i><p>Aucun client trouvé</p>
|
||||
<a href="/clients/new" class="text-orange-500 hover:underline text-sm mt-2 inline-block">Ajouter le premier →</a>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-users"></i>
|
||||
<p>Aucun client trouvé</p>
|
||||
<a href="/clients/new" class="link-action">Ajouter le premier →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
+89
-59
@@ -1,89 +1,119 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dashboard - GarageManager{% endblock %}
|
||||
{% block title %}Dashboard — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold text-slate-800">Dashboard</h2>
|
||||
<p class="text-slate-500 mt-1">Vue d'ensemble de votre garage</p>
|
||||
|
||||
<div class="page-header flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="page-title"><i class="fas fa-chart-line text-electric" style="margin-right:0.5rem"></i>Dashboard</h1>
|
||||
<p class="page-subtitle">Vue d'ensemble de votre garage</p>
|
||||
</div>
|
||||
<a href="/interventions/new" class="btn-primary">
|
||||
<i class="fas fa-plus"></i> Nouvelle intervention
|
||||
</a>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><p class="text-sm text-slate-500">Véhicules</p><p class="text-3xl font-bold text-slate-800 mt-1">{{ stats.nb_vehicules }}</p></div>
|
||||
<div class="bg-purple-100 p-3 rounded-full"><i class="fas fa-car text-purple-500 text-xl"></i></div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="grid grid-4 mb-8">
|
||||
<div class="card" style="cursor:default">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="stat-label">Véhicules</div>
|
||||
<div style="width:38px;height:38px;background:rgba(0,212,255,0.1);border-radius:8px;display:flex;align-items:center;justify-content:center">
|
||||
<i class="fas fa-car text-electric"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/vehicules" class="text-xs text-purple-500 hover:underline mt-3 inline-block">Voir tous →</a>
|
||||
<div class="stat-value">{{ stats.nb_vehicules }}</div>
|
||||
<a href="/vehicules" class="link-action text-xs mt-2" style="display:inline-block">Voir tous →</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><p class="text-sm text-slate-500">En attente</p><p class="text-3xl font-bold text-yellow-500 mt-1">{{ stats.nb_en_attente }}</p></div>
|
||||
<div class="bg-yellow-100 p-3 rounded-full"><i class="fas fa-clock text-yellow-500 text-xl"></i></div>
|
||||
|
||||
<div class="card" style="cursor:default">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="stat-label">Interventions</div>
|
||||
<div style="width:38px;height:38px;background:rgba(250,204,21,0.1);border-radius:8px;display:flex;align-items:center;justify-content:center">
|
||||
<i class="fas fa-tools text-volt"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/interventions?statut=en_attente" class="text-xs text-yellow-500 hover:underline mt-3 inline-block">Voir →</a>
|
||||
<div class="stat-value" style="color:var(--accent-volt)">{{ stats.nb_interventions }}</div>
|
||||
<a href="/interventions" class="link-action text-xs mt-2" style="display:inline-block;color:var(--accent-volt)">Voir toutes →</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><p class="text-sm text-slate-500">En cours</p><p class="text-3xl font-bold text-blue-500 mt-1">{{ stats.nb_en_cours }}</p></div>
|
||||
<div class="bg-blue-100 p-3 rounded-full"><i class="fas fa-spinner text-blue-500 text-xl"></i></div>
|
||||
|
||||
<div class="card" style="cursor:default">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="stat-label">Pièces en stock</div>
|
||||
<div style="width:38px;height:38px;background:rgba(34,197,94,0.1);border-radius:8px;display:flex;align-items:center;justify-content:center">
|
||||
<i class="fas fa-cog text-green"></i>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/interventions?statut=en_cours" class="text-xs text-blue-500 hover:underline mt-3 inline-block">Voir →</a>
|
||||
<div class="stat-value" style="color:var(--accent-green)">{{ stats.nb_pieces_stock }}</div>
|
||||
<span class="text-xs text-muted mt-2" style="display:inline-block">Pièces référencées</span>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><p class="text-sm text-slate-500">Terminées</p><p class="text-3xl font-bold text-green-500 mt-1">{{ stats.nb_termine }}</p></div>
|
||||
<div class="bg-green-100 p-3 rounded-full"><i class="fas fa-check-circle text-green-500 text-xl"></i></div>
|
||||
</div>
|
||||
<a href="/interventions?statut=termine" class="text-xs text-green-500 hover:underline mt-3 inline-block">Voir →</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><p class="text-sm text-slate-500">Coût pièces</p><p class="text-3xl font-bold text-slate-800 mt-1">{{ "%.0f"|format(stats.cout_pieces) }} €</p></div>
|
||||
<div class="bg-red-100 p-3 rounded-full"><i class="fas fa-cog text-red-400 text-xl"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div><p class="text-sm text-slate-500">Total dépensé</p><p class="text-3xl font-bold text-orange-500 mt-1">{{ "%.0f"|format(stats.cout_total) }} €</p></div>
|
||||
<div class="bg-orange-100 p-3 rounded-full"><i class="fas fa-euro-sign text-orange-500 text-xl"></i></div>
|
||||
|
||||
<div class="card" style="cursor:default;border-color:rgba(250,204,21,0.2)">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="stat-label" style="color:var(--accent-volt)">Total dépensé</div>
|
||||
<div style="width:38px;height:38px;background:rgba(250,204,21,0.1);border-radius:8px;display:flex;align-items:center;justify-content:center">
|
||||
<i class="fas fa-euro-sign text-volt"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-value" style="color:var(--accent-volt)">{{ "%.0f"|format(stats.cout_total) }} €</div>
|
||||
<span class="text-xs text-muted mt-2" style="display:inline-block">Toutes interventions</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow">
|
||||
<div class="p-6 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-slate-800">Interventions récentes</h3>
|
||||
<a href="/interventions/new" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600 transition">+ Nouvelle</a>
|
||||
|
||||
<!-- Interventions récentes -->
|
||||
<div class="card" style="padding:0">
|
||||
<div class="card-header">
|
||||
<span class="card-title"><i class="fas fa-clock-rotate-left text-electric" style="margin-right:0.4rem"></i>Interventions récentes</span>
|
||||
<a href="/interventions/new" class="btn-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> Nouvelle
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if recent %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-slate-600 text-xs uppercase">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left">Véhicule</th>
|
||||
<th class="px-6 py-3 text-left">Description</th>
|
||||
<th class="px-6 py-3 text-left">Statut</th>
|
||||
<th class="px-6 py-3 text-left">Coût total</th>
|
||||
<th>Véhicule</th>
|
||||
<th>Description</th>
|
||||
<th>Date</th>
|
||||
<th>Kilométrage</th>
|
||||
<th>Pièces</th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<tbody>
|
||||
{% for i in recent %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 font-medium text-slate-800"><a href="/vehicules/{{ i.vehicule_id }}" class="hover:text-orange-500">{{ i.immatriculation }}<br><span class="text-xs font-normal text-slate-400">{{ i.marque }} {{ i.modele }}</span></a></td>
|
||||
<td class="px-6 py-4 text-slate-600"><a href="/interventions/{{ i.id }}" class="hover:text-orange-500">{{ i.description[:60] }}{% if i.description|length > 60 %}...{% endif %}</a></td>
|
||||
<td class="px-6 py-4">
|
||||
{% if i.statut == 'en_attente' %}<span class="bg-yellow-100 text-yellow-700 px-2 py-1 rounded-full text-xs">En attente</span>
|
||||
{% elif i.statut == 'en_cours' %}<span class="bg-blue-100 text-blue-700 px-2 py-1 rounded-full text-xs">En cours</span>
|
||||
{% else %}<span class="bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs">Terminé</span>{% endif %}
|
||||
<tr style="cursor:pointer" onclick="window.location='/interventions/{{ i.id }}'">
|
||||
<td>
|
||||
<span class="font-semibold mono" style="color:var(--accent-electric);font-size:0.85rem">{{ i.immatriculation }}</span>
|
||||
<br>
|
||||
<span class="text-xs text-muted">{{ i.marque }} {{ i.modele }}</span>
|
||||
</td>
|
||||
<td style="max-width:280px">
|
||||
<span class="truncate" style="display:block;max-width:280px">{{ i.description[:60] }}{% if i.description|length > 60 %}…{% endif %}</span>
|
||||
</td>
|
||||
<td class="text-xs text-secondary">{{ i.date_intervention or '—' }}</td>
|
||||
<td class="text-xs text-secondary mono">{{ (i.kilometrage_intervention|string + ' km') if i.kilometrage_intervention else '—' }}</td>
|
||||
<td>
|
||||
<span class="text-xs text-secondary">{{ i.nb_pieces }} pièce(s)</span>
|
||||
<br>
|
||||
<span class="text-xs text-muted">{{ "%.0f"|format(i.cout_pieces) }} €</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span class="font-semibold" style="color:var(--accent-volt)">{{ "%.0f"|format(i.cout_main_oeuvre + i.cout_pieces) }} €</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 font-semibold text-slate-700">{{ "%.0f"|format(i.cout_main_oeuvre + i.cout_pieces) }} €</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-8 text-center text-slate-400">
|
||||
<i class="fas fa-tools text-3xl mb-3"></i><p>Aucune intervention pour l'instant</p>
|
||||
<a href="/interventions/new" class="text-orange-500 hover:underline text-sm mt-2 inline-block">Créer la première →</a>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-tools"></i>
|
||||
<p>Aucune intervention pour l'instant</p>
|
||||
<a href="/interventions/new" class="link-action">Créer la première →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,142 +1,203 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Intervention #{{ intervention.id }} - GarageManager{% endblock %}
|
||||
{% block title %}Intervention — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-6">
|
||||
<a href="/vehicules/{{ intervention.vid }}" class="text-slate-500 hover:text-slate-700 text-sm flex items-center gap-1 mb-4"><i class="fas fa-arrow-left"></i> Retour au véhicule</a>
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800">{{ intervention.description }}</h2>
|
||||
<p class="text-slate-500 mt-1">{{ intervention.immatriculation }} — {{ intervention.marque }} {{ intervention.modele }}</p>
|
||||
<p class="text-xs text-slate-400 mt-1">{{ intervention.created_at[:10] }}{% if intervention.technicien %} • <i class="fas fa-user-cog mr-1"></i>{{ intervention.technicien }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
{% if intervention.statut == 'en_attente' %}<span class="bg-yellow-100 text-yellow-700 px-3 py-1 rounded-full text-sm">⏳ En attente</span>
|
||||
{% elif intervention.statut == 'en_cours' %}<span class="bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm">🔧 En cours</span>
|
||||
{% else %}<span class="bg-green-100 text-green-700 px-3 py-1 rounded-full text-sm">✅ Terminé</span>{% endif %}
|
||||
<a href="/interventions/{{ intervention.id }}/edit" class="border border-orange-300 text-orange-500 px-4 py-2 rounded-lg text-sm hover:bg-orange-50">Modifier</a>
|
||||
<form method="post" action="/interventions/{{ intervention.id }}/delete" onsubmit="return confirm('Supprimer cette intervention ?')">
|
||||
<button class="border border-red-300 text-red-500 px-4 py-2 rounded-lg text-sm hover:bg-red-50">Supprimer</button>
|
||||
</form>
|
||||
|
||||
<a href="/vehicules/{{ intervention.vid }}" class="back-link"><i class="fas fa-arrow-left"></i> Retour au véhicule</a>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-start mb-8">
|
||||
<div>
|
||||
<h1 class="page-title" style="max-width:600px">{{ intervention.description }}</h1>
|
||||
<p class="page-subtitle" style="margin-top:0.4rem">
|
||||
<a href="/vehicules/{{ intervention.vid }}" class="mono" style="color:var(--accent-electric);text-decoration:none;font-size:0.95rem">{{ intervention.immatriculation }}</a>
|
||||
<span class="text-muted"> — {{ intervention.marque }} {{ intervention.modele }}</span>
|
||||
</p>
|
||||
<div class="flex gap-4 mt-2" style="flex-wrap:wrap">
|
||||
{% if intervention.date_intervention %}
|
||||
<span class="text-xs text-secondary"><i class="fas fa-calendar mr-1"></i>{{ intervention.date_intervention }}</span>
|
||||
{% endif %}
|
||||
{% if intervention.kilometrage_intervention %}
|
||||
<span class="text-xs text-secondary mono"><i class="fas fa-tachometer-alt mr-1"></i>{{ intervention.kilometrage_intervention }} km</span>
|
||||
{% endif %}
|
||||
{% if intervention.technicien %}
|
||||
<span class="text-xs text-secondary"><i class="fas fa-user-cog mr-1"></i>{{ intervention.technicien }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/interventions/{{ intervention.id }}/edit" class="btn-secondary btn-sm">
|
||||
<i class="fas fa-edit"></i> Modifier
|
||||
</a>
|
||||
<form method="post" action="/interventions/{{ intervention.id }}/delete" onsubmit="return confirm('Supprimer cette intervention ?')">
|
||||
<button class="btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i> Supprimer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Résumé coûts -->
|
||||
<div class="grid grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl shadow p-5">
|
||||
<p class="text-xs text-slate-500">Main d'œuvre</p>
|
||||
<p class="text-2xl font-bold text-slate-800 mt-1">{{ "%.2f"|format(intervention.cout_main_oeuvre) }} €</p>
|
||||
<div class="grid grid-3 mb-6">
|
||||
<div class="card" style="cursor:default">
|
||||
<div class="stat-label mb-2">Main d'œuvre</div>
|
||||
<div class="stat-value" style="color:var(--accent-electric)">{{ "%.2f"|format(intervention.cout_main_oeuvre) }} €</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-5">
|
||||
<p class="text-xs text-slate-500">Pièces</p>
|
||||
<p class="text-2xl font-bold text-slate-800 mt-1">{{ "%.2f"|format(intervention.cout_pieces) }} €</p>
|
||||
<div class="card" style="cursor:default">
|
||||
<div class="stat-label mb-2">Pièces</div>
|
||||
<div class="stat-value" style="color:var(--text-secondary)">{{ "%.2f"|format(intervention.cout_pieces) }} €</div>
|
||||
</div>
|
||||
<div class="bg-orange-50 rounded-xl shadow p-5 border border-orange-200">
|
||||
<p class="text-xs text-orange-600">Total</p>
|
||||
<p class="text-2xl font-bold text-orange-600 mt-1">{{ "%.2f"|format(intervention.cout_main_oeuvre + intervention.cout_pieces) }} €</p>
|
||||
<div class="card" style="cursor:default;border-color:rgba(250,204,21,0.25)">
|
||||
<div class="stat-label mb-2" style="color:var(--accent-volt)">Total</div>
|
||||
<div class="stat-value" style="color:var(--accent-volt)">{{ "%.2f"|format(intervention.cout_main_oeuvre + intervention.cout_pieces) }} €</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if intervention.notes %}
|
||||
<div class="bg-white rounded-xl shadow p-5 mb-6">
|
||||
<h3 class="font-semibold text-slate-800 mb-2">📋 Notes</h3>
|
||||
<p class="text-sm text-slate-600 whitespace-pre-wrap">{{ intervention.notes }}</p>
|
||||
<div class="card mb-6">
|
||||
<h3 class="card-title mb-3"><i class="fas fa-clipboard-list text-secondary" style="margin-right:0.4rem"></i>Notes</h3>
|
||||
<p class="text-sm" style="white-space:pre-wrap;color:var(--text-secondary)">{{ intervention.notes }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Pièces -->
|
||||
<div class="bg-white rounded-xl shadow">
|
||||
<div class="p-5 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-slate-800">🔩 Pièces utilisées ({{ pieces|length }})</h3>
|
||||
<button onclick="document.getElementById('add-piece-form').classList.toggle('hidden')" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600">+ Ajouter une pièce</button>
|
||||
<!-- ── Pièces utilisées ── -->
|
||||
<div class="card" style="padding:0">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<i class="fas fa-cog text-volt" style="margin-right:0.4rem"></i>
|
||||
Pièces utilisées
|
||||
<span class="badge badge-warning ml-2">{{ pieces_utilisees|length }}</span>
|
||||
</span>
|
||||
<button onclick="toggleAddPiece()" class="btn-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> Ajouter une pièce
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire ajout pièce -->
|
||||
<div id="add-piece-form" class="hidden p-5 border-b border-gray-100 bg-gray-50">
|
||||
<!-- Form ajout pièce -->
|
||||
<div id="add-piece-form" style="display:none;background:var(--bg-secondary);border-bottom:1px solid var(--border);padding:1.25rem 1.5rem">
|
||||
<form method="post" action="/interventions/{{ intervention.id }}/pieces/add" enctype="multipart/form-data">
|
||||
<div class="grid grid-cols-2 gap-3 mb-3">
|
||||
|
||||
{% if pieces_connues %}
|
||||
<div class="mb-4">
|
||||
<p class="text-xs font-semibold uppercase" style="color:var(--text-muted);letter-spacing:0.06em;margin-bottom:0.5rem">
|
||||
🔄 Réutiliser une pièce connue sur ce véhicule
|
||||
</p>
|
||||
<div class="scroll-list">
|
||||
{% for p in pieces_connues %}
|
||||
<label class="scroll-list-item">
|
||||
<input type="radio" name="_prefill" value="{{ loop.index0 }}"
|
||||
data-nom="{{ p.nom }}" data-ref="{{ p.reference or '' }}" data-marque="{{ p.marque or '' }}" data-prix="{{ p.prix }}"
|
||||
onchange="prefillPiece(this)"
|
||||
style="width:auto;background:none;border:none;padding:0">
|
||||
<span class="font-medium text-sm" style="flex:1">{{ p.nom }}</span>
|
||||
{% if p.reference %}<span class="mono text-xs text-muted">{{ p.reference }}</span>{% endif %}
|
||||
{% if p.marque %}<span class="text-xs text-secondary">{{ p.marque }}</span>{% endif %}
|
||||
<span class="font-semibold text-sm text-electric mono">{{ "%.2f"|format(p.prix) }} €</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="text-xs text-muted mt-1">Les infos se pré-remplissent automatiquement, tu peux les modifier.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-2 mb-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Nom de la pièce *</label>
|
||||
<input name="nom" required placeholder="Ex: Filtre à huile, Courroie..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Nom *</label>
|
||||
<input id="p-nom" name="nom" required placeholder="Nom de la pièce">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Référence</label>
|
||||
<input name="reference" placeholder="Ex: OC90, 06A115561B..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Référence</label>
|
||||
<input id="p-ref" name="reference" placeholder="Ex: 06A115561B…" style="font-family:'JetBrains Mono',monospace">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3 mb-3">
|
||||
<div class="grid grid-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Marque</label>
|
||||
<input name="marque" placeholder="Ex: Bosch, NGK, Valeo..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Marque</label>
|
||||
<input id="p-marque" name="marque" placeholder="Bosch, NGK…">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Prix unitaire (€)</label>
|
||||
<input name="prix" type="number" step="0.01" min="0" value="0" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Prix unitaire (€)</label>
|
||||
<input id="p-prix" name="prix" type="number" step="0.01" min="0" value="0" class="mono">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Quantité</label>
|
||||
<input name="quantite" type="number" min="1" value="1" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Quantité</label>
|
||||
<input name="quantite" type="number" min="1" value="1" class="mono">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Photo (optionnel)</label>
|
||||
<input type="file" name="photo" accept="image/*" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm">
|
||||
<label>Photo (optionnel)</label>
|
||||
<input type="file" name="photo" accept="image/*" style="background:none;border:none;padding:0.25rem 0;color:var(--text-secondary)">
|
||||
</div>
|
||||
<button type="submit" class="bg-orange-500 text-white px-5 py-2 rounded-lg text-sm hover:bg-orange-600">Ajouter la pièce</button>
|
||||
<button type="submit" class="btn-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> Ajouter la pièce
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Liste des pièces -->
|
||||
{% if pieces %}
|
||||
{% if pieces_utilisees %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-slate-600 text-xs uppercase">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-5 py-3 text-left">Photo</th>
|
||||
<th class="px-5 py-3 text-left">Pièce</th>
|
||||
<th class="px-5 py-3 text-left">Référence</th>
|
||||
<th class="px-5 py-3 text-left">Marque</th>
|
||||
<th class="px-5 py-3 text-left">Qté</th>
|
||||
<th class="px-5 py-3 text-left">Prix unit.</th>
|
||||
<th class="px-5 py-3 text-left">Total</th>
|
||||
<th class="px-5 py-3 text-left"></th>
|
||||
<th>Photo</th>
|
||||
<th>Pièce</th>
|
||||
<th>Référence</th>
|
||||
<th>Marque</th>
|
||||
<th class="text-center">Qté</th>
|
||||
<th class="text-right">Prix unit.</th>
|
||||
<th class="text-right">Total</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
{% for p in pieces %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-5 py-3">
|
||||
<tbody>
|
||||
{% for p in pieces_utilisees %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if p.photo %}
|
||||
<img src="{{ p.photo }}" alt="{{ p.nom }}" class="w-12 h-12 object-cover rounded-lg cursor-pointer border" onclick="window.open('{{ p.photo }}','_blank')">
|
||||
<img src="{{ p.photo }}" class="photo-thumb" style="width:48px;height:48px" onclick="window.open('{{ p.photo }}','_blank')">
|
||||
{% else %}
|
||||
<div class="w-12 h-12 bg-gray-100 rounded-lg flex items-center justify-center text-slate-300"><i class="fas fa-cog"></i></div>
|
||||
<div class="photo-placeholder" style="width:48px;height:48px"><i class="fas fa-cog"></i></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-5 py-3 font-medium text-slate-800">{{ p.nom }}</td>
|
||||
<td class="px-5 py-3 text-slate-500 font-mono text-xs">{{ p.reference or '-' }}</td>
|
||||
<td class="px-5 py-3 text-slate-600">{{ p.marque or '-' }}</td>
|
||||
<td class="px-5 py-3 text-slate-600">{{ p.quantite }}</td>
|
||||
<td class="px-5 py-3 text-slate-600">{{ "%.2f"|format(p.prix) }} €</td>
|
||||
<td class="px-5 py-3 font-semibold text-slate-800">{{ "%.2f"|format(p.prix * p.quantite) }} €</td>
|
||||
<td class="px-5 py-3">
|
||||
<td class="font-medium">{{ p.nom }}</td>
|
||||
<td class="mono text-xs text-muted">{{ p.reference or '—' }}</td>
|
||||
<td class="text-secondary text-sm">{{ p.marque or '—' }}</td>
|
||||
<td class="text-center text-secondary mono">{{ p.quantite }}</td>
|
||||
<td class="text-right mono text-sm">{{ "%.2f"|format(p.prix) }} €</td>
|
||||
<td class="text-right font-semibold mono" style="color:var(--accent-volt)">{{ "%.2f"|format(p.prix * p.quantite) }} €</td>
|
||||
<td>
|
||||
<form method="post" action="/interventions/{{ intervention.id }}/pieces/{{ p.id }}/delete" onsubmit="return confirm('Supprimer cette pièce ?')">
|
||||
<button class="text-red-400 hover:text-red-600 text-xs hover:underline">Supprimer</button>
|
||||
<button class="link-danger">✕</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="bg-orange-50">
|
||||
<td colspan="6" class="px-5 py-3 text-right font-semibold text-slate-700">Total pièces</td>
|
||||
<td colspan="2" class="px-5 py-3 font-bold text-orange-600">{{ "%.2f"|format(intervention.cout_pieces) }} €</td>
|
||||
<tr style="background:var(--bg-secondary)">
|
||||
<td colspan="6" class="text-right font-semibold text-secondary text-sm">Total pièces</td>
|
||||
<td class="text-right font-bold mono" style="color:var(--accent-volt)">{{ "%.2f"|format(intervention.cout_pieces) }} €</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-8 text-center text-slate-400 text-sm">
|
||||
Aucune pièce enregistrée.<br>
|
||||
<button onclick="document.getElementById('add-piece-form').classList.remove('hidden')" class="text-orange-500 hover:underline mt-1 inline-block">Ajouter la première →</button>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-cog"></i>
|
||||
<p>Aucune pièce pour cette intervention</p>
|
||||
<button onclick="toggleAddPiece()" class="link-action">Ajouter →</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleAddPiece() {
|
||||
const f = document.getElementById('add-piece-form');
|
||||
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
function prefillPiece(radio) {
|
||||
document.getElementById('p-nom').value = radio.dataset.nom;
|
||||
document.getElementById('p-ref').value = radio.dataset.ref;
|
||||
document.getElementById('p-marque').value = radio.dataset.marque;
|
||||
document.getElementById('p-prix').value = radio.dataset.prix;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,51 +1,68 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{% if intervention %}Modifier{% else %}Nouvelle{% endif %} intervention - GarageManager{% endblock %}
|
||||
{% block title %}{% if intervention %}Modifier{% else %}Nouvelle{% endif %} intervention — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-8">
|
||||
<a href="/interventions" class="text-slate-500 hover:text-slate-700 text-sm flex items-center gap-1 mb-4"><i class="fas fa-arrow-left"></i> Retour</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800">{% if intervention %}Modifier l'intervention{% else %}Nouvelle intervention{% endif %}</h2>
|
||||
|
||||
<a href="/interventions" class="back-link"><i class="fas fa-arrow-left"></i> Retour aux interventions</a>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">
|
||||
<i class="fas fa-{% if intervention %}edit{% else %}plus-circle{% endif %} text-electric" style="margin-right:0.5rem"></i>
|
||||
{% if intervention %}Modifier l'intervention{% else %}Nouvelle intervention{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-8 max-w-2xl">
|
||||
<form method="post" class="space-y-4">
|
||||
|
||||
<div class="card" style="max-width:680px">
|
||||
<form method="post" style="display:flex;flex-direction:column;gap:1.25rem">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Véhicule *</label>
|
||||
<select name="vehicule_id" required class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<option value="">-- Sélectionner un véhicule --</option>
|
||||
<label>Véhicule *</label>
|
||||
<select name="vehicule_id" required>
|
||||
<option value="">— Sélectionner un véhicule —</option>
|
||||
{% for v in vehicules %}
|
||||
<option value="{{ v.id }}" {% if (intervention and intervention.vehicule_id == v.id) or preselect_vehicule == v.id %}selected{% endif %}>{{ v.label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Description *</label>
|
||||
<textarea name="description" rows="2" required placeholder="Ex: Vidange + filtre à huile, Changement courroie de distribution..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">{{ intervention.description if intervention else '' }}</textarea>
|
||||
<label>Description *</label>
|
||||
<textarea name="description" rows="2" required placeholder="Ex: Vidange + filtre à huile, Changement courroie de distribution…">{{ intervention.description if intervention else '' }}</textarea>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Statut</label>
|
||||
<select name="statut" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<option value="en_attente" {% if not intervention or intervention.statut == 'en_attente' %}selected{% endif %}>⏳ En attente</option>
|
||||
<option value="en_cours" {% if intervention and intervention.statut == 'en_cours' %}selected{% endif %}>🔧 En cours</option>
|
||||
<option value="termine" {% if intervention and intervention.statut == 'termine' %}selected{% endif %}>✅ Terminé</option>
|
||||
</select>
|
||||
<label>Date de l'intervention</label>
|
||||
<input name="date_intervention" type="date" value="{{ intervention.date_intervention if intervention and intervention.date_intervention else '' }}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Main d'œuvre (€)</label>
|
||||
<input name="cout_main_oeuvre" type="number" step="0.01" min="0" value="{{ intervention.cout_main_oeuvre if intervention else '0' }}" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Kilométrage (km)</label>
|
||||
<input name="kilometrage_intervention" type="number" value="{{ intervention.kilometrage_intervention if intervention and intervention.kilometrage_intervention else '' }}" placeholder="85000" class="mono">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Technicien</label>
|
||||
<input name="technicien" value="{{ intervention.technicien if intervention else '' }}" placeholder="Nom du technicien / garage" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<label>Main d'œuvre (€)</label>
|
||||
<input name="cout_main_oeuvre" type="number" step="0.01" min="0" value="{{ intervention.cout_main_oeuvre if intervention else '0' }}" class="mono">
|
||||
</div>
|
||||
<div>
|
||||
<label>Technicien / Garage</label>
|
||||
<input name="technicien" value="{{ intervention.technicien if intervention else '' }}" placeholder="Nom du garage, soi-même…">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Notes</label>
|
||||
<textarea name="notes" rows="3" placeholder="Observations, prochaine révision prévue, problèmes restants..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">{{ intervention.notes if intervention else '' }}</textarea>
|
||||
<label>Notes</label>
|
||||
<textarea name="notes" rows="3" placeholder="Observations, prochaine révision, problèmes restants…">{{ intervention.notes if intervention else '' }}</textarea>
|
||||
</div>
|
||||
<div class="flex gap-3 pt-4">
|
||||
<button type="submit" class="bg-orange-500 text-white px-6 py-2 rounded-lg hover:bg-orange-600 transition text-sm font-medium">Enregistrer</button>
|
||||
<a href="/interventions" class="text-slate-500 px-6 py-2 rounded-lg border border-gray-200 hover:bg-gray-50 text-sm">Annuler</a>
|
||||
|
||||
<div style="display:flex;gap:0.75rem;padding-top:0.5rem;border-top:1px solid var(--border)">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save"></i> Enregistrer
|
||||
</button>
|
||||
<a href="/interventions" class="btn-secondary">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,63 +1,77 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Interventions - GarageManager{% endblock %}
|
||||
{% block title %}Interventions — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div><h2 class="text-2xl font-bold text-slate-800">Interventions</h2><p class="text-slate-500 mt-1">{{ interventions|length }} intervention(s)</p></div>
|
||||
<a href="/interventions/new" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600 flex items-center gap-2"><i class="fas fa-plus"></i> Nouvelle intervention</a>
|
||||
</div>
|
||||
<div class="flex gap-2 mb-6 flex-wrap">
|
||||
<a href="/interventions" class="px-4 py-2 rounded-full text-sm {% if not statut %}bg-slate-800 text-white{% else %}bg-white text-slate-600 border hover:bg-gray-50{% endif %}">Toutes</a>
|
||||
<a href="/interventions?statut=en_attente" class="px-4 py-2 rounded-full text-sm {% if statut == 'en_attente' %}bg-yellow-500 text-white{% else %}bg-white text-slate-600 border hover:bg-gray-50{% endif %}">⏳ En attente</a>
|
||||
<a href="/interventions?statut=en_cours" class="px-4 py-2 rounded-full text-sm {% if statut == 'en_cours' %}bg-blue-500 text-white{% else %}bg-white text-slate-600 border hover:bg-gray-50{% endif %}">🔧 En cours</a>
|
||||
<a href="/interventions?statut=termine" class="px-4 py-2 rounded-full text-sm {% if statut == 'termine' %}bg-green-500 text-white{% else %}bg-white text-slate-600 border hover:bg-gray-50{% endif %}">✅ Terminées</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow overflow-x-auto">
|
||||
{% if interventions %}
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-slate-600 text-xs uppercase">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left">Véhicule</th>
|
||||
<th class="px-6 py-3 text-left">Description</th>
|
||||
<th class="px-6 py-3 text-left">Statut</th>
|
||||
<th class="px-6 py-3 text-left">Pièces</th>
|
||||
<th class="px-6 py-3 text-left">Total</th>
|
||||
<th class="px-6 py-3 text-left">Date</th>
|
||||
<th class="px-6 py-3 text-left">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
{% for i in interventions %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 font-medium text-slate-800">
|
||||
<a href="/vehicules/{{ i.vehicule_id }}" class="hover:text-orange-500">{{ i.immatriculation }}<br><span class="text-xs font-normal text-slate-400">{{ i.marque }} {{ i.modele }}</span></a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-slate-600 max-w-xs">
|
||||
<a href="/interventions/{{ i.id }}" class="hover:text-orange-500">{{ i.description[:60] }}{% if i.description|length > 60 %}...{% endif %}</a>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
{% if i.statut == 'en_attente' %}<span class="bg-yellow-100 text-yellow-700 px-2 py-1 rounded-full text-xs">En attente</span>
|
||||
{% elif i.statut == 'en_cours' %}<span class="bg-blue-100 text-blue-700 px-2 py-1 rounded-full text-xs">En cours</span>
|
||||
{% else %}<span class="bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs">Terminé</span>{% endif %}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-slate-500 text-xs">{{ "%.2f"|format(i.cout_pieces) }} €</td>
|
||||
<td class="px-6 py-4 font-semibold text-slate-700">{{ "%.0f"|format(i.cout_main_oeuvre + i.cout_pieces) }} €</td>
|
||||
<td class="px-6 py-4 text-slate-400 text-xs">{{ i.created_at[:10] }}</td>
|
||||
<td class="px-6 py-4 flex gap-3">
|
||||
<a href="/interventions/{{ i.id }}" class="text-blue-500 hover:underline">Voir</a>
|
||||
<a href="/interventions/{{ i.id }}/edit" class="text-orange-500 hover:underline">Modifier</a>
|
||||
<form method="post" action="/interventions/{{ i.id }}/delete" class="inline" onsubmit="return confirm('Supprimer ?')">
|
||||
<button class="text-red-500 hover:underline">Supprimer</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-8 text-center text-slate-400">
|
||||
<i class="fas fa-tools text-3xl mb-3"></i><p>Aucune intervention trouvée</p>
|
||||
<a href="/interventions/new" class="text-orange-500 hover:underline text-sm mt-2 inline-block">Créer la première →</a>
|
||||
|
||||
<div class="page-header flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="page-title"><i class="fas fa-tools text-electric" style="margin-right:0.5rem"></i>Interventions</h1>
|
||||
<p class="page-subtitle">{{ interventions|length }} intervention(s)</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<a href="/interventions/new" class="btn-primary">
|
||||
<i class="fas fa-plus"></i> Nouvelle intervention
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card" style="padding:0">
|
||||
<div class="overflow-x-auto">
|
||||
{% if interventions %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Véhicule</th>
|
||||
<th>Description</th>
|
||||
<th>Date</th>
|
||||
<th>Kilométrage</th>
|
||||
<th>Pièces</th>
|
||||
<th class="text-right">Total</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in interventions %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/vehicules/{{ i.vehicule_id }}" style="text-decoration:none">
|
||||
<span class="font-semibold mono" style="color:var(--accent-electric);font-size:0.85rem">{{ i.immatriculation }}</span>
|
||||
<br>
|
||||
<span class="text-xs text-muted">{{ i.marque }} {{ i.modele }}</span>
|
||||
</a>
|
||||
</td>
|
||||
<td style="max-width:260px">
|
||||
<a href="/interventions/{{ i.id }}" class="font-medium" style="text-decoration:none;color:var(--text-primary)">
|
||||
{{ i.description[:60] }}{% if i.description|length > 60 %}…{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-secondary text-xs">{{ i.date_intervention or '—' }}</td>
|
||||
<td class="mono text-xs text-secondary">{{ (i.kilometrage_intervention|string + ' km') if i.kilometrage_intervention else '—' }}</td>
|
||||
<td>
|
||||
<span class="text-xs text-secondary">{{ i.nb_pieces }} pièce(s)</span><br>
|
||||
<span class="text-xs text-muted">{{ "%.2f"|format(i.cout_pieces) }} €</span>
|
||||
</td>
|
||||
<td class="text-right font-semibold" style="color:var(--accent-volt)">
|
||||
{{ "%.0f"|format(i.cout_main_oeuvre + i.cout_pieces) }} €
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-3">
|
||||
<a href="/interventions/{{ i.id }}" class="link-action">Voir</a>
|
||||
<a href="/interventions/{{ i.id }}/edit" class="link-edit">Modifier</a>
|
||||
<form method="post" action="/interventions/{{ i.id }}/delete" style="display:inline" onsubmit="return confirm('Supprimer cette intervention ?')">
|
||||
<button class="link-danger">Supprimer</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-tools"></i>
|
||||
<p>Aucune intervention pour l'instant</p>
|
||||
<a href="/interventions/new" class="link-action">Créer la première →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
+219
-99
@@ -1,128 +1,248 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ vehicule.immatriculation }} - GarageManager{% endblock %}
|
||||
{% block title %}{{ vehicule.immatriculation }} — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-6">
|
||||
<a href="/vehicules" class="text-slate-500 hover:text-slate-700 text-sm flex items-center gap-1 mb-4"><i class="fas fa-arrow-left"></i> Retour aux véhicules</a>
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-slate-800">{{ vehicule.immatriculation }}</h2>
|
||||
<p class="text-slate-500 mt-1">{{ vehicule.marque }} {{ vehicule.modele }}{% if vehicule.annee %} ({{ vehicule.annee }}){% endif %}{% if vehicule.kilometrage %} • {{ vehicule.kilometrage }} km{% endif %}</p>
|
||||
{% if vehicule.vin %}<p class="text-xs font-mono text-slate-400 mt-1">VIN : {{ vehicule.vin }}</p>{% endif %}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/interventions/new?vehicule_id={{ vehicule.id }}" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600">+ Intervention</a>
|
||||
<a href="/vehicules/{{ vehicule.id }}/edit" class="border border-orange-300 text-orange-500 px-4 py-2 rounded-lg text-sm hover:bg-orange-50">Modifier</a>
|
||||
<form method="post" action="/vehicules/{{ vehicule.id }}/delete" onsubmit="return confirm('Supprimer ce véhicule ?')">
|
||||
<button class="border border-red-300 text-red-500 px-4 py-2 rounded-lg text-sm hover:bg-red-50">Supprimer</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<a href="/vehicules" class="back-link"><i class="fas fa-arrow-left"></i> Retour aux véhicules</a>
|
||||
|
||||
<!-- Header véhicule -->
|
||||
<div class="flex justify-between items-start mb-8">
|
||||
<div>
|
||||
<h1 class="page-title mono" style="color:var(--accent-electric);font-size:2rem;letter-spacing:0.06em">{{ vehicule.immatriculation }}</h1>
|
||||
<p class="page-subtitle" style="margin-top:0.35rem">
|
||||
{{ vehicule.marque }} {{ vehicule.modele }}
|
||||
{% if vehicule.annee %} — {{ vehicule.annee }}{% endif %}
|
||||
{% if vehicule.kilometrage %} — <span class="mono">{{ vehicule.kilometrage }} km</span>{% endif %}
|
||||
</p>
|
||||
{% if vehicule.vin %}
|
||||
<p class="text-xs text-muted mono" style="margin-top:0.35rem">VIN : {{ vehicule.vin }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/interventions/new?vehicule_id={{ vehicule.id }}" class="btn-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> Intervention
|
||||
</a>
|
||||
<a href="/vehicules/{{ vehicule.id }}/edit" class="btn-secondary btn-sm">
|
||||
<i class="fas fa-edit"></i> Modifier
|
||||
</a>
|
||||
<form method="post" action="/vehicules/{{ vehicule.id }}/delete" onsubmit="return confirm('Supprimer ce véhicule et toutes ses données ?')">
|
||||
<button class="btn-danger btn-sm">
|
||||
<i class="fas fa-trash"></i> Supprimer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Infos / Notes -->
|
||||
<div class="bg-white rounded-xl shadow mb-6">
|
||||
<div class="p-5 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-slate-800">📎 Infos & Notes</h3>
|
||||
<button onclick="document.getElementById('add-info-form').classList.toggle('hidden')" class="text-orange-500 text-sm hover:underline">+ Ajouter</button>
|
||||
<!-- ── Historique interventions ── -->
|
||||
<div class="card mb-6" style="padding:0" id="interventions">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<i class="fas fa-tools text-electric" style="margin-right:0.4rem"></i>
|
||||
Historique interventions
|
||||
<span class="badge badge-info ml-2">{{ interventions|length }}</span>
|
||||
</span>
|
||||
<a href="/interventions/new?vehicule_id={{ vehicule.id }}" class="link-action text-xs">+ Nouvelle</a>
|
||||
</div>
|
||||
<div id="add-info-form" class="hidden p-5 border-b border-gray-100 bg-gray-50">
|
||||
<form method="post" action="/vehicules/{{ vehicule.id }}/infos/add" enctype="multipart/form-data" class="space-y-3">
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Type</label>
|
||||
<select name="type" id="info-type" onchange="toggleInfoType()" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm">
|
||||
<option value="texte">📝 Texte</option>
|
||||
<option value="url">🔗 URL / Lien</option>
|
||||
<option value="image">🖼️ Image</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Titre (optionnel)</label>
|
||||
<input name="titre" placeholder="Ex: Manuel d'entretien, Lien forum..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<div id="input-texte">
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Contenu *</label>
|
||||
<textarea name="contenu" rows="3" placeholder="Notes libres, infos techniques..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm"></textarea>
|
||||
</div>
|
||||
<div id="input-url" class="hidden">
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">URL *</label>
|
||||
<input name="contenu" placeholder="https://..." class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div id="input-image" class="hidden">
|
||||
<label class="block text-xs font-medium text-slate-600 mb-1">Photo *</label>
|
||||
<input type="file" name="photo" accept="image/*" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm">
|
||||
<input type="hidden" name="contenu" value="photo">
|
||||
</div>
|
||||
<button type="submit" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600">Enregistrer</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if infos %}
|
||||
<div class="divide-y divide-gray-100">
|
||||
{% for info in infos %}
|
||||
<div class="px-5 py-4 flex justify-between items-start hover:bg-gray-50">
|
||||
<div class="flex-1">
|
||||
{% if info.titre %}<p class="text-xs font-semibold text-slate-500 uppercase mb-1">{{ info.titre }}</p>{% endif %}
|
||||
{% if info.type == 'texte' %}
|
||||
<p class="text-sm text-slate-700 whitespace-pre-wrap">{{ info.contenu }}</p>
|
||||
{% elif info.type == 'url' %}
|
||||
<a href="{{ info.contenu }}" target="_blank" class="text-sm text-blue-500 hover:underline break-all">🔗 {{ info.contenu }}</a>
|
||||
{% elif info.type == 'image' %}
|
||||
<img src="{{ info.contenu }}" alt="{{ info.titre or 'image' }}" class="max-h-48 rounded-lg mt-1 cursor-pointer" onclick="window.open('{{ info.contenu }}','_blank')">
|
||||
{% endif %}
|
||||
<p class="text-xs text-slate-400 mt-1">{{ info.created_at[:10] }}</p>
|
||||
</div>
|
||||
<form method="post" action="/vehicules/{{ vehicule.id }}/infos/{{ info.id }}/delete" class="ml-4" onsubmit="return confirm('Supprimer ?')">
|
||||
<button class="text-red-400 hover:text-red-600 text-xs">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if interventions %}
|
||||
<div class="overflow-x-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Date</th>
|
||||
<th>Kilométrage</th>
|
||||
<th class="text-right">Pièces</th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in interventions %}
|
||||
<tr style="cursor:pointer" onclick="window.location='/interventions/{{ i.id }}'">
|
||||
<td>
|
||||
<p class="font-medium">{{ i.description }}</p>
|
||||
{% if i.technicien %}<p class="text-xs text-muted mt-1">{{ i.technicien }}</p>{% endif %}
|
||||
</td>
|
||||
<td class="text-secondary text-sm">{{ i.date_intervention or '—' }}</td>
|
||||
<td class="mono text-sm text-secondary">{{ (i.kilometrage_intervention|string + ' km') if i.kilometrage_intervention else '—' }}</td>
|
||||
<td class="text-right">
|
||||
<span class="text-xs text-secondary">{{ i.nb_pieces }} pce(s)</span><br>
|
||||
<span class="text-xs text-muted">{{ "%.2f"|format(i.cout_pieces) }} €</span>
|
||||
</td>
|
||||
<td class="text-right font-semibold" style="color:var(--accent-volt)">
|
||||
{{ "%.0f"|format(i.cout_main_oeuvre + i.cout_pieces) }} €
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr style="background:var(--bg-secondary)">
|
||||
<td colspan="4" class="text-right text-secondary font-semibold text-sm" style="padding:0.75rem 1rem">Total véhicule</td>
|
||||
<td class="text-right font-bold" style="color:var(--accent-volt);padding:0.75rem 1rem">
|
||||
{{ "%.0f"|format(interventions|sum(attribute='cout_main_oeuvre') + interventions|sum(attribute='cout_pieces')) }} €
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-6 text-center text-slate-400 text-sm">Aucune info pour l'instant.</div>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-tools"></i>
|
||||
<p>Aucune intervention</p>
|
||||
<a href="/interventions/new?vehicule_id={{ vehicule.id }}" class="link-action">Créer →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Interventions -->
|
||||
<div class="bg-white rounded-xl shadow">
|
||||
<div class="p-5 border-b border-gray-100 flex justify-between items-center">
|
||||
<h3 class="font-semibold text-slate-800">🔧 Interventions ({{ interventions|length }})</h3>
|
||||
<a href="/interventions/new?vehicule_id={{ vehicule.id }}" class="text-orange-500 text-sm hover:underline">+ Nouvelle intervention</a>
|
||||
<!-- ── Toutes les pièces ── -->
|
||||
<div class="card mb-6" style="padding:0" id="pieces">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<i class="fas fa-cog text-volt" style="margin-right:0.4rem"></i>
|
||||
Toutes les pièces utilisées
|
||||
<span class="badge badge-warning ml-2">{{ toutes_pieces|length }}</span>
|
||||
</span>
|
||||
</div>
|
||||
{% if interventions %}
|
||||
<div class="divide-y divide-gray-100">
|
||||
{% for i in interventions %}
|
||||
<div class="px-5 py-4 hover:bg-gray-50">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<a href="/interventions/{{ i.id }}" class="font-medium text-slate-800 hover:text-orange-500">{{ i.description }}</a>
|
||||
{% if i.technicien %}<p class="text-xs text-slate-400 mt-1"><i class="fas fa-user-cog mr-1"></i>{{ i.technicien }}</p>{% endif %}
|
||||
<p class="text-xs text-slate-400 mt-1">{{ i.created_at[:10] }} • {{ i.nb_pieces }} pièce(s)</p>
|
||||
<p class="text-xs text-muted" style="padding:0.6rem 1.5rem;border-bottom:1px solid var(--border)">Récapitulatif de toutes les pièces de toutes les interventions — pour les retrouver et racheter facilement.</p>
|
||||
|
||||
{% if toutes_pieces %}
|
||||
<div class="overflow-x-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Photo</th>
|
||||
<th>Pièce</th>
|
||||
<th>Référence</th>
|
||||
<th>Marque</th>
|
||||
<th>Prix unit.</th>
|
||||
<th class="text-center">Qté</th>
|
||||
<th>Intervention</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in toutes_pieces %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if p.photo %}
|
||||
<img src="{{ p.photo }}" class="photo-thumb" onclick="window.open('{{ p.photo }}','_blank')">
|
||||
{% else %}
|
||||
<div class="photo-placeholder"><i class="fas fa-cog text-xs"></i></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="font-medium">{{ p.nom }}</td>
|
||||
<td class="mono text-xs text-muted">{{ p.reference or '—' }}</td>
|
||||
<td class="text-secondary text-sm">{{ p.marque or '—' }}</td>
|
||||
<td class="mono text-sm">{{ "%.2f"|format(p.prix) }} €</td>
|
||||
<td class="text-center text-secondary">{{ p.quantite }}</td>
|
||||
<td class="text-xs text-secondary" style="max-width:200px">
|
||||
<a href="/interventions/{{ p.intervention_id }}" class="link-action truncate" style="display:block;max-width:200px">
|
||||
{{ p.intervention_desc[:40] }}{% if p.intervention_desc|length > 40 %}…{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-xs text-muted">{{ p.date_intervention or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-cog"></i>
|
||||
<p>Aucune pièce enregistrée</p>
|
||||
<span class="text-xs text-muted">Les pièces s'affichent ici dès qu'elles sont ajoutées dans une intervention.</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- ── Infos & Notes ── -->
|
||||
<div class="card" style="padding:0" id="infos">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<i class="fas fa-paperclip text-secondary" style="margin-right:0.4rem"></i>
|
||||
Infos & Notes
|
||||
</span>
|
||||
<button onclick="toggleAddInfo()" class="link-action text-xs">+ Ajouter</button>
|
||||
</div>
|
||||
|
||||
<!-- Form ajout info -->
|
||||
<div id="add-info-form" style="display:none;background:var(--bg-secondary);border-bottom:1px solid var(--border);padding:1.25rem 1.5rem">
|
||||
<form method="post" action="/vehicules/{{ vehicule.id }}/infos/add" enctype="multipart/form-data">
|
||||
<div class="grid grid-3 mb-3">
|
||||
<div>
|
||||
<label>Type</label>
|
||||
<select name="type" id="info-type" onchange="toggleInfoType()">
|
||||
<option value="texte">📝 Texte libre</option>
|
||||
<option value="url">🔗 Lien / URL</option>
|
||||
<option value="image">🖼️ Image / Photo</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 ml-4 flex-shrink-0">
|
||||
<span class="text-sm font-semibold text-slate-700">{{ "%.0f"|format(i.cout_main_oeuvre + i.cout_pieces) }} €</span>
|
||||
{% if i.statut == 'en_attente' %}<span class="bg-yellow-100 text-yellow-700 px-2 py-1 rounded-full text-xs">En attente</span>
|
||||
{% elif i.statut == 'en_cours' %}<span class="bg-blue-100 text-blue-700 px-2 py-1 rounded-full text-xs">En cours</span>
|
||||
{% else %}<span class="bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs">Terminé</span>{% endif %}
|
||||
<div style="grid-column:span 2">
|
||||
<label>Titre (optionnel)</label>
|
||||
<input name="titre" placeholder="Ex: Forum, Fiche technique, Photo moteur…">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div id="input-texte">
|
||||
<label>Contenu</label>
|
||||
<textarea name="contenu" rows="2" placeholder="Notes, infos techniques…"></textarea>
|
||||
</div>
|
||||
<div id="input-url" style="display:none">
|
||||
<label>URL</label>
|
||||
<input name="contenu" placeholder="https://…">
|
||||
</div>
|
||||
<div id="input-image" style="display:none">
|
||||
<label>Photo</label>
|
||||
<input type="file" name="photo" accept="image/*" style="background:none;border:none;padding:0.25rem 0;color:var(--text-secondary)">
|
||||
<input type="hidden" name="contenu" value="photo">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary btn-sm">
|
||||
<i class="fas fa-save"></i> Enregistrer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if infos %}
|
||||
<div>
|
||||
{% for info in infos %}
|
||||
<div style="padding:1rem 1.5rem;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:flex-start;transition:background 0.15s" onmouseover="this.style.background='rgba(0,212,255,0.03)'" onmouseout="this.style.background=''">
|
||||
<div style="flex:1">
|
||||
{% if info.titre %}
|
||||
<p class="text-xs font-semibold uppercase" style="letter-spacing:0.06em;color:var(--text-muted);margin-bottom:0.3rem">{{ info.titre }}</p>
|
||||
{% endif %}
|
||||
{% if info.type == 'texte' %}
|
||||
<p class="text-sm" style="white-space:pre-wrap;color:var(--text-primary)">{{ info.contenu }}</p>
|
||||
{% elif info.type == 'url' %}
|
||||
<a href="{{ info.contenu }}" target="_blank" class="link-action text-sm" style="word-break:break-all">🔗 {{ info.contenu }}</a>
|
||||
{% elif info.type == 'image' %}
|
||||
<img src="{{ info.contenu }}" style="max-height:160px;border-radius:8px;border:1px solid var(--border);margin-top:0.25rem;cursor:pointer" onclick="window.open('{{ info.contenu }}','_blank')">
|
||||
{% endif %}
|
||||
<p class="text-xs text-muted" style="margin-top:0.35rem">{{ info.created_at[:10] }}</p>
|
||||
</div>
|
||||
<form method="post" action="/vehicules/{{ vehicule.id }}/infos/{{ info.id }}/delete" style="margin-left:1rem" onsubmit="return confirm('Supprimer ?')">
|
||||
<button class="link-danger">✕</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="p-6 text-center text-slate-400 text-sm">
|
||||
Aucune intervention.<br>
|
||||
<a href="/interventions/new?vehicule_id={{ vehicule.id }}" class="text-orange-500 hover:underline mt-1 inline-block">Créer la première →</a>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-paperclip"></i>
|
||||
<p>Aucune info pour l'instant</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleAddInfo() {
|
||||
const f = document.getElementById('add-info-form');
|
||||
f.style.display = f.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
function toggleInfoType() {
|
||||
const type = document.getElementById('info-type').value;
|
||||
document.getElementById('input-texte').classList.toggle('hidden', type !== 'texte');
|
||||
document.getElementById('input-url').classList.toggle('hidden', type !== 'url');
|
||||
document.getElementById('input-image').classList.toggle('hidden', type !== 'image');
|
||||
document.getElementById('input-texte').style.display = type === 'texte' ? 'block' : 'none';
|
||||
document.getElementById('input-url').style.display = type === 'url' ? 'block' : 'none';
|
||||
document.getElementById('input-image').style.display = type === 'image' ? 'block' : 'none';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,44 +1,59 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{% if vehicule %}Modifier{% else %}Nouveau{% endif %} véhicule - GarageManager{% endblock %}
|
||||
{% block title %}{% if vehicule %}Modifier{% else %}Nouveau{% endif %} véhicule — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="mb-8">
|
||||
<a href="/vehicules" class="text-slate-500 hover:text-slate-700 text-sm flex items-center gap-1 mb-4"><i class="fas fa-arrow-left"></i> Retour</a>
|
||||
<h2 class="text-2xl font-bold text-slate-800">{% if vehicule %}Modifier le véhicule{% else %}Nouveau véhicule{% endif %}</h2>
|
||||
|
||||
<a href="/vehicules" class="back-link"><i class="fas fa-arrow-left"></i> Retour aux véhicules</a>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">
|
||||
<i class="fas fa-{% if vehicule %}edit{% else %}plus-circle{% endif %} text-electric" style="margin-right:0.5rem"></i>
|
||||
{% if vehicule %}Modifier le véhicule{% else %}Nouveau véhicule{% endif %}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow p-8 max-w-2xl">
|
||||
<form method="post" class="space-y-4">
|
||||
|
||||
<div class="card" style="max-width:640px">
|
||||
<form method="post" class="space" style="display:flex;flex-direction:column;gap:1.25rem">
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Immatriculation *</label>
|
||||
<input name="immatriculation" value="{{ vehicule.immatriculation if vehicule else '' }}" required placeholder="AB-123-CD" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300 uppercase">
|
||||
<label>Immatriculation *</label>
|
||||
<input name="immatriculation" value="{{ vehicule.immatriculation if vehicule else '' }}" required placeholder="AB-123-CD" style="text-transform:uppercase;font-family:'JetBrains Mono',monospace;font-size:1rem;letter-spacing:0.05em">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Marque *</label>
|
||||
<input name="marque" value="{{ vehicule.marque if vehicule else '' }}" required class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Marque *</label>
|
||||
<input name="marque" value="{{ vehicule.marque if vehicule else '' }}" required placeholder="Renault, Peugeot…">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Modèle *</label>
|
||||
<input name="modele" value="{{ vehicule.modele if vehicule else '' }}" required class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Modèle *</label>
|
||||
<input name="modele" value="{{ vehicule.modele if vehicule else '' }}" required placeholder="Clio, 308…">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
<div class="grid grid-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Année</label>
|
||||
<input name="annee" type="number" value="{{ vehicule.annee if vehicule else '' }}" min="1900" max="2030" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Année</label>
|
||||
<input name="annee" type="number" value="{{ vehicule.annee if vehicule else '' }}" min="1900" max="2030" placeholder="2019">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">Kilométrage</label>
|
||||
<input name="kilometrage" type="number" value="{{ vehicule.kilometrage if vehicule else '' }}" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<label>Kilométrage (km)</label>
|
||||
<input name="kilometrage" type="number" value="{{ vehicule.kilometrage if vehicule else '' }}" placeholder="85000">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1">VIN (numéro de châssis)</label>
|
||||
<input name="vin" value="{{ vehicule.vin if vehicule else '' }}" placeholder="17 caractères" maxlength="17" class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-orange-300 uppercase">
|
||||
<label>VIN (numéro de châssis)</label>
|
||||
<input name="vin" value="{{ vehicule.vin if vehicule else '' }}" placeholder="17 caractères" maxlength="17" style="text-transform:uppercase;font-family:'JetBrains Mono',monospace;letter-spacing:0.08em">
|
||||
<span class="text-xs text-muted" style="margin-top:0.3rem;display:block">Format : 17 caractères alphanumériques</span>
|
||||
</div>
|
||||
<div class="flex gap-3 pt-4">
|
||||
<button type="submit" class="bg-orange-500 text-white px-6 py-2 rounded-lg hover:bg-orange-600 transition text-sm font-medium">Enregistrer</button>
|
||||
<a href="/vehicules" class="text-slate-500 px-6 py-2 rounded-lg border border-gray-200 hover:bg-gray-50 text-sm">Annuler</a>
|
||||
|
||||
<div style="display:flex;gap:0.75rem;padding-top:0.5rem;border-top:1px solid var(--border)">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-save"></i> Enregistrer
|
||||
</button>
|
||||
<a href="/vehicules" class="btn-secondary">Annuler</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
+60
-38
@@ -1,58 +1,80 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Véhicules - GarageManager{% endblock %}
|
||||
{% block title %}Véhicules — GarageManager{% endblock %}
|
||||
{% block content %}
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div><h2 class="text-2xl font-bold text-slate-800">Véhicules</h2><p class="text-slate-500 mt-1">{{ vehicules|length }} véhicule(s)</p></div>
|
||||
<a href="/vehicules/new" class="bg-orange-500 text-white px-4 py-2 rounded-lg text-sm hover:bg-orange-600 transition flex items-center gap-2"><i class="fas fa-plus"></i> Nouveau véhicule</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow">
|
||||
<div class="p-4 border-b border-gray-100">
|
||||
<form method="get" class="flex gap-2">
|
||||
<input name="q" value="{{ q }}" placeholder="Immatriculation, marque, modèle, VIN..." class="flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-orange-300">
|
||||
<button type="submit" class="bg-slate-700 text-white px-4 py-2 rounded-lg text-sm">Rechercher</button>
|
||||
{% if q %}<a href="/vehicules" class="text-slate-500 px-3 py-2 text-sm flex items-center">✕</a>{% endif %}
|
||||
</form>
|
||||
|
||||
<div class="page-header flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="page-title"><i class="fas fa-car text-electric" style="margin-right:0.5rem"></i>Véhicules</h1>
|
||||
<p class="page-subtitle">{{ vehicules|length }} véhicule(s) enregistré(s)</p>
|
||||
</div>
|
||||
<a href="/vehicules/new" class="btn-primary">
|
||||
<i class="fas fa-plus"></i> Nouveau véhicule
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="card mb-4" style="padding:1rem 1.25rem">
|
||||
<form method="get" class="flex gap-2">
|
||||
<input name="q" value="{{ q }}" placeholder="Immatriculation, marque, modèle, VIN…" style="flex:1">
|
||||
<button type="submit" class="btn-secondary" style="width:auto;white-space:nowrap">
|
||||
<i class="fas fa-search"></i> Rechercher
|
||||
</button>
|
||||
{% if q %}
|
||||
<a href="/vehicules" class="btn-secondary" style="width:auto">✕</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card" style="padding:0">
|
||||
<div class="overflow-x-auto">
|
||||
{% if vehicules %}
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 text-slate-600 text-xs uppercase">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left">Immatriculation</th>
|
||||
<th class="px-6 py-3 text-left">Marque / Modèle</th>
|
||||
<th class="px-6 py-3 text-left">Année</th>
|
||||
<th class="px-6 py-3 text-left">VIN</th>
|
||||
<th class="px-6 py-3 text-left">Kilométrage</th>
|
||||
<th class="px-6 py-3 text-left">Interventions</th>
|
||||
<th class="px-6 py-3 text-left">Actions</th>
|
||||
<th>Immatriculation</th>
|
||||
<th>Marque / Modèle</th>
|
||||
<th>Année</th>
|
||||
<th>VIN</th>
|
||||
<th>Kilométrage</th>
|
||||
<th>Interventions</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<tbody>
|
||||
{% for v in vehicules %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 font-bold text-slate-800">{{ v.immatriculation }}</td>
|
||||
<td class="px-6 py-4 font-medium text-slate-700">{{ v.marque }} {{ v.modele }}</td>
|
||||
<td class="px-6 py-4 text-slate-500">{{ v.annee or '-' }}</td>
|
||||
<td class="px-6 py-4 text-slate-400 text-xs font-mono">{{ v.vin or '-' }}</td>
|
||||
<td class="px-6 py-4 text-slate-600">{{ (v.kilometrage|string + ' km') if v.kilometrage else '-' }}</td>
|
||||
<td class="px-6 py-4"><span class="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs">{{ v.nb_interventions }}</span></td>
|
||||
<td class="px-6 py-4 flex gap-3">
|
||||
<a href="/vehicules/{{ v.id }}" class="text-blue-500 hover:underline">Voir</a>
|
||||
<a href="/vehicules/{{ v.id }}/edit" class="text-orange-500 hover:underline">Modifier</a>
|
||||
<form method="post" action="/vehicules/{{ v.id }}/delete" class="inline" onsubmit="return confirm('Supprimer ce véhicule ?')">
|
||||
<button class="text-red-500 hover:underline">Supprimer</button>
|
||||
</form>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/vehicules/{{ v.id }}" class="font-bold mono" style="color:var(--accent-electric);font-size:0.95rem;text-decoration:none">{{ v.immatriculation }}</a>
|
||||
</td>
|
||||
<td class="font-medium">{{ v.marque }} {{ v.modele }}</td>
|
||||
<td class="text-secondary">{{ v.annee or '—' }}</td>
|
||||
<td class="mono text-xs text-muted">{{ v.vin or '—' }}</td>
|
||||
<td class="mono text-sm">{{ (v.kilometrage|string + ' km') if v.kilometrage else '—' }}</td>
|
||||
<td>
|
||||
<span class="badge badge-info">{{ v.nb_interventions }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-3">
|
||||
<a href="/vehicules/{{ v.id }}" class="link-action">Voir</a>
|
||||
<a href="/vehicules/{{ v.id }}/edit" class="link-edit">Modifier</a>
|
||||
<form method="post" action="/vehicules/{{ v.id }}/delete" style="display:inline" onsubmit="return confirm('Supprimer ce véhicule et toutes ses données ?')">
|
||||
<button class="link-danger">Supprimer</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="p-8 text-center text-slate-400">
|
||||
<i class="fas fa-car text-3xl mb-3"></i><p>Aucun véhicule trouvé</p>
|
||||
<a href="/vehicules/new" class="text-orange-500 hover:underline text-sm mt-2 inline-block">Ajouter le premier →</a>
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-car"></i>
|
||||
<p>Aucun véhicule trouvé</p>
|
||||
<a href="/vehicules/new" class="link-action">Ajouter le premier →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user