design: refonte UI — journal de bord (Playfair + JetBrains Mono, palette encre/crème/ambre)

This commit is contained in:
2026-03-11 18:46:04 +01:00
parent 23dbc3f870
commit c3a7bdfef7
6 changed files with 453 additions and 164 deletions

View File

@@ -3,23 +3,242 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Tableau de bord{% endblock %}</title>
<title>{% block title %}Journal de bord{% endblock %}</title>
<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=Playfair+Display:wght@600;700&family=JetBrains+Mono:wght@400;500;600&family=Lato:wght@300;400;600&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<style>
:root {
--ink: #1A2332;
--cream: #F7F4EE;
--parchment: #EDE9DF;
--amber: #C8842A;
--amber-lt: #F0A94A;
--amber-pale:#FDF3E3;
--sage: #4A7A54;
--sage-pale: #EBF4ED;
--rust: #B85035;
--rust-pale: #FCEEE9;
--mist: #D5D0C8;
}
* { box-sizing: border-box; }
body {
font-family: 'Lato', sans-serif;
background-color: var(--cream);
color: var(--ink);
min-height: 100vh;
/* subtle dot grid */
background-image: radial-gradient(circle, rgba(180,170,155,0.35) 1px, transparent 1px);
background-size: 22px 22px;
}
.font-display { font-family: 'Playfair Display', Georgia, serif; }
.font-data { font-family: 'JetBrains Mono', monospace; }
/* ── Cards ─────────────────────────────── */
.card {
background: #ffffff;
border-left: 3px solid transparent;
border-radius: 3px;
padding: 1.1rem 1rem;
box-shadow: 0 1px 3px rgba(26,35,50,0.07);
margin-bottom: 0.85rem;
}
.card-amber { border-left-color: var(--amber); }
.card-sage { border-left-color: var(--sage); }
.card-ink { border-left-color: var(--ink); }
.card-rust { border-left-color: var(--rust); }
.card-mist { border-left-color: var(--mist); }
.card-label {
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #8A8278;
margin-bottom: 0.45rem;
}
/* ── Big stat number ───────────────────── */
.stat-number {
font-family: 'JetBrains Mono', monospace;
font-size: 2.25rem;
font-weight: 600;
line-height: 1;
color: var(--ink);
}
.stat-sub {
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
color: #8A8278;
}
/* ── Balance badge ─────────────────────── */
.balance-pos { color: var(--sage); background: var(--sage-pale); }
.balance-neg { color: var(--rust); background: var(--rust-pale); }
.balance-badge {
font-family: 'JetBrains Mono', monospace;
font-size: 0.85rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 2px;
}
/* ── Progress bar ──────────────────────── */
.progress-track {
height: 4px;
background: var(--parchment);
border-radius: 2px;
overflow: hidden;
margin-top: 6px;
}
.progress-fill {
height: 100%;
border-radius: 2px;
transition: width 0.5s ease;
}
/* ── Nav ───────────────────────────────── */
nav { background-color: var(--ink); }
.nav-link {
color: rgba(247,244,238,0.65);
font-size: 0.82rem;
font-weight: 400;
letter-spacing: 0.04em;
padding-bottom: 2px;
border-bottom: 1.5px solid transparent;
transition: color 0.15s, border-color 0.15s;
}
.nav-link:hover {
color: #F7F4EE;
border-bottom-color: var(--amber);
}
/* ── Inputs ────────────────────────────── */
.field-input {
width: 100%;
background: #FAFAF8;
border: 1px solid var(--mist);
border-radius: 3px;
padding: 0.55rem 0.75rem;
font-size: 0.875rem;
color: var(--ink);
font-family: 'Lato', sans-serif;
transition: border-color 0.15s, box-shadow 0.15s;
}
.field-input:focus {
outline: none;
border-color: var(--amber);
box-shadow: 0 0 0 3px rgba(200,132,42,0.12);
}
.field-label {
display: block;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.09em;
text-transform: uppercase;
color: #8A8278;
margin-bottom: 0.4rem;
}
/* ── Buttons ───────────────────────────── */
.btn-primary {
background-color: var(--ink);
color: var(--cream);
font-weight: 600;
font-size: 0.875rem;
letter-spacing: 0.04em;
padding: 0.75rem 1.5rem;
border-radius: 3px;
border: none;
cursor: pointer;
transition: background-color 0.15s, transform 0.1s;
width: 100%;
text-align: center;
display: block;
}
.btn-primary:hover { background-color: #243147; }
.btn-primary:active { transform: scale(0.99); }
.btn-amber {
background-color: var(--amber);
color: #fff;
}
.btn-amber:hover { background-color: var(--amber-lt); }
.btn-ghost {
background-color: transparent;
color: #8A8278;
border: 1px solid var(--mist);
font-weight: 600;
font-size: 0.875rem;
padding: 0.75rem 1.5rem;
border-radius: 3px;
cursor: pointer;
text-align: center;
display: block;
width: 100%;
transition: background-color 0.15s;
}
.btn-ghost:hover { background-color: var(--parchment); }
/* ── Pill radio (type journée) ─────────── */
.pill-option { display: block; cursor: pointer; }
.pill-option input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; }
.pill-inner {
display: block;
text-align: center;
font-size: 0.75rem;
font-weight: 600;
padding: 0.45rem 0.25rem;
border: 1.5px solid var(--mist);
border-radius: 3px;
color: #8A8278;
background: #FAFAF8;
transition: all 0.12s;
letter-spacing: 0.02em;
}
.pill-option input:checked + .pill-inner {
border-color: var(--ink);
background-color: var(--ink);
color: var(--cream);
}
.pill-inner:hover { border-color: #AAA49C; color: var(--ink); }
/* ── Divider line ──────────────────────── */
.ruled { border-color: var(--parchment); }
/* ── Day type dot (entry list) ─────────── */
.dt-dot {
width: 7px;
height: 7px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 5px;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<nav class="bg-blue-700 text-white px-4 py-3 flex items-center justify-between">
<a href="/" class="font-bold text-lg">⏱ Temps de travail</a>
<div class="flex gap-4 text-sm">
<a href="/entries/new" class="hover:underline">+ Saisir</a>
<a href="/entries/" class="hover:underline">Historique</a>
<a href="/reports/" class="hover:underline">Rapports</a>
<body>
<nav class="px-4 py-3 flex items-center justify-between sticky top-0 z-10">
<a href="/" class="font-display text-white text-base font-semibold" style="letter-spacing:0.015em;">
Journal de bord
</a>
<div class="flex gap-5">
<a href="/entries/new" class="nav-link">Saisir</a>
<a href="/entries/" class="nav-link">Historique</a>
<a href="/reports/" class="nav-link">Rapports</a>
</div>
</nav>
<main class="max-w-2xl mx-auto px-4 py-6">
<main class="max-w-xl mx-auto px-4 py-5">
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="mb-4 p-3 rounded {% if category == 'error' %}bg-red-100 text-red-800{% else %}bg-green-100 text-green-800{% endif %}">
<div class="mb-4 px-4 py-2.5 text-sm rounded-sm"
style="{% if category == 'error' %}border-left:3px solid var(--rust);background:var(--rust-pale);color:var(--rust){% else %}border-left:3px solid var(--sage);background:var(--sage-pale);color:var(--sage){% endif %}">
{{ message }}
</div>
{% endfor %}

View File

@@ -1,71 +1,97 @@
{% extends "base.html" %}
{% block title %}Tableau de bord{% endblock %}
{% block title %}Journal de bord{% endblock %}
{% block content %}
<div class="bg-white rounded-xl shadow p-4 mb-4">
<h2 class="font-semibold text-gray-700 mb-2">
Aujourd'hui — {{ today | date_fr }}
</h2>
<!-- En-tête date -->
<div class="mb-5">
<p class="font-display text-2xl font-semibold" style="color:var(--ink); letter-spacing:-0.01em;">
{{ today | date_fr }}
</p>
</div>
<!-- Carte aujourd'hui -->
<div class="card card-amber">
<p class="card-label">Aujourd'hui</p>
{% if today_entry %}
<p class="text-green-700 font-medium">{{ today_entry.day_type }} — {{ today_entry.total_hours_str() }}</p>
<div class="flex items-baseline gap-3">
<span class="stat-number">{{ today_entry.total_hours_str() }}</span>
<span class="stat-sub">travaillées</span>
</div>
<div class="flex items-center justify-between mt-3">
<span class="text-xs font-semibold px-2 py-0.5 rounded" style="background:var(--parchment); color:#6A6258; letter-spacing:0.06em;">
{{ today_entry.day_type }}{% if today_entry.motor_vehicle_id %} · {{ today_entry.motor_vehicle_id }}{% endif %}
</span>
<a href="/entries/{{ today_entry.id }}/edit"
class="mt-2 inline-block text-sm text-blue-600 hover:underline">Modifier</a>
class="text-xs font-semibold" style="color:var(--amber);">Modifier</a>
</div>
{% if today_entry.comment %}
<p class="mt-2 text-xs italic" style="color:#9A9288;">{{ today_entry.comment }}</p>
{% endif %}
{% else %}
<a href="/entries/new"
class="block w-full text-center bg-blue-600 text-white py-3 rounded-lg font-semibold text-lg hover:bg-blue-700 active:bg-blue-800 transition">
<a href="/entries/new" class="btn-primary btn-amber mt-1" style="letter-spacing:0.06em;">
+ Saisir ma journée
</a>
{% endif %}
</div>
<div class="bg-white rounded-xl shadow p-4 mb-4">
<h2 class="font-semibold text-gray-700 mb-3">Semaine courante</h2>
<div class="flex justify-between items-center">
<span class="text-gray-600">Total</span>
<span class="font-bold text-xl">{{ week_actual_str }}</span>
<!-- Carte semaine -->
<div class="card card-ink">
<p class="card-label">Semaine en cours</p>
<div class="flex items-end justify-between">
<div>
<span class="stat-number">{{ week_actual_str }}</span>
<p class="stat-sub mt-1">objectif 38h45</p>
</div>
<div class="flex justify-between items-center mt-1">
<span class="text-gray-600">Écart vs 38h45</span>
<span class="font-medium {% if week_balance >= 0 %}text-green-600{% else %}text-red-600{% endif %}">
{{ '+' if week_balance >= 0 else '-' }}{{ week_balance_str }}
<span class="balance-badge {% if week_balance >= 0 %}balance-pos{% else %}balance-neg{% endif %}">
{{ '+' if week_balance >= 0 else '' }}{{ week_balance_str }}
</span>
</div>
</div>
<div class="bg-white rounded-xl shadow p-4 mb-4">
<h2 class="font-semibold text-gray-700 mb-3">Ce mois</h2>
<!-- Carte mois -->
{% if month_km %}
<div class="card card-mist">
<p class="card-label">Ce mois — déplacements</p>
<div class="space-y-2 mt-1">
{% for vehicle_id, km in month_km.items() %}
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>{{ vehicle_id | capitalize }}</span>
<span>{{ km }} km</span>
<div class="flex items-baseline justify-between">
<span class="text-xs" style="color:#8A8278;">{{ vehicle_id | capitalize }}</span>
<span class="font-data font-semibold text-sm" style="color:var(--ink);">{{ km }} <span class="font-normal text-xs" style="color:#9A9288;">km</span></span>
</div>
{% endfor %}
<div class="flex justify-between text-sm text-gray-600 mt-2">
<span>CO₂</span>
<span>{{ month_co2_kg }} kg</span>
<div class="flex items-baseline justify-between pt-1" style="border-top:1px solid var(--parchment);">
<span class="text-xs" style="color:#8A8278;">CO₂</span>
<span class="font-data font-semibold text-sm" style="color:var(--ink);">{{ month_co2_kg }} <span class="font-normal text-xs" style="color:#9A9288;">kg</span></span>
</div>
</div>
</div>
{% endif %}
<div class="bg-white rounded-xl shadow p-4">
<h2 class="font-semibold text-gray-700 mb-3">Congés / RTT</h2>
<div class="mb-3">
<div class="flex justify-between text-sm mb-1">
<span>Congés</span>
<span>{{ used.conges }} / {{ balance.conges_total }} j</span>
<!-- Carte congés / RTT -->
<div class="card card-sage">
<p class="card-label">Solde congés &amp; RTT</p>
<div class="space-y-3 mt-1">
<div>
<div class="flex justify-between items-baseline">
<span class="text-xs font-semibold" style="color:#8A8278; letter-spacing:0.06em;">CONGÉS</span>
<span class="font-data text-sm font-semibold" style="color:var(--ink);">
{{ used.conges }}<span class="font-normal text-xs" style="color:#9A9288;"> / {{ balance.conges_total }} j</span>
</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-blue-500 h-2 rounded-full"
style="width: {{ [[used.conges / balance.conges_total * 100, 100] | min, 0] | max }}%"></div>
<div class="progress-track">
<div class="progress-fill" style="width:{{ [[used.conges / balance.conges_total * 100, 100] | min, 0] | max }}%; background:var(--sage);"></div>
</div>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span>RTT</span>
<span>{{ used.rtt }} / {{ balance.rtt_total }} j</span>
<div class="flex justify-between items-baseline">
<span class="text-xs font-semibold" style="color:#8A8278; letter-spacing:0.06em;">RTT</span>
<span class="font-data text-sm font-semibold" style="color:var(--ink);">
{{ used.rtt }}<span class="font-normal text-xs" style="color:#9A9288;"> / {{ balance.rtt_total }} j</span>
</span>
</div>
<div class="progress-track">
<div class="progress-fill" style="width:{{ [[used.rtt / balance.rtt_total * 100, 100] | min, 0] | max }}%; background:var(--amber);"></div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-purple-500 h-2 rounded-full"
style="width: {{ [[used.rtt / balance.rtt_total * 100, 100] | min, 0] | max }}%"></div>
</div>
</div>
</div>

View File

@@ -1,45 +1,45 @@
{% extends "base.html" %}
{% block title %}{% if entry %}Modifier{% else %}Nouvelle entrée{% endif %}{% endblock %}
{% block title %}{% if entry %}Modifier l'entrée{% else %}Nouvelle journée{% endif %}{% endblock %}
{% block content %}
<h1 class="text-xl font-bold mb-4">
{% if entry %}Modifier le {{ entry.date }}{% else %}Nouvelle journée{% endif %}
</h1>
<div class="mb-5">
<p class="font-display text-2xl font-semibold" style="color:var(--ink);">
{% if entry %}Modifier{% else %}Nouvelle journée{% endif %}
</p>
</div>
<form method="POST" class="space-y-5">
<!-- Date -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<label class="field-label">Date</label>
<input type="date" name="date"
value="{{ entry.date.isoformat() if entry else today }}"
class="w-full border rounded-lg px-3 py-2 text-sm" required>
class="field-input" required>
</div>
<!-- Type de journée -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Type de journée</label>
<label class="field-label">Type de journée</label>
<div class="grid grid-cols-3 gap-2">
{% for value, label in day_types %}
<label class="cursor-pointer">
<label class="pill-option">
<input type="radio" name="day_type" value="{{ value }}"
{% if entry and entry.day_type == value %}checked{% elif not entry and value == 'WORK' %}checked{% endif %}
class="sr-only peer"
onchange="updateJourneyVisibility(this.value)">
<div class="text-center text-sm py-2 px-1 rounded-lg border-2 border-gray-200
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:text-blue-700
hover:border-gray-300 transition">
{{ label }}
</div>
<span class="pill-inner">{{ label }}</span>
</label>
{% endfor %}
</div>
</div>
<!-- Trajet domicile-travail -->
<div id="journey-section"
class="{% if entry and entry.day_type in day_types_without_journey %}hidden{% endif %}">
<label class="block text-sm font-medium text-gray-700 mb-1">Trajet domicile-travail</label>
<label class="field-label">Trajet domicile-travail</label>
<select name="journey_profile_id" id="journey_profile_id"
onchange="updateMotorVehicleVisibility(this.value)"
class="w-full border rounded-lg px-3 py-2 text-sm">
class="field-input">
<option value="">— Pas de déplacement —</option>
{% for jid, jdata in journeys.items() %}
<option value="{{ jid }}"
@@ -52,68 +52,67 @@
</select>
</div>
<!-- Véhicule à moteur -->
<div id="motor-vehicle-section" class="{% if not entry or not entry.motor_vehicle_id %}hidden{% endif %}">
<label class="block text-sm font-medium text-gray-700 mb-1">Véhicule à moteur utilisé</label>
<label class="field-label">Véhicule à moteur utilisé</label>
<div class="grid grid-cols-2 gap-2">
{% for vid, vdata in motor_vehicles.items() %}
<label class="cursor-pointer">
<label class="pill-option">
<input type="radio" name="motor_vehicle_id" value="{{ vid }}"
{% if entry and entry.motor_vehicle_id == vid %}checked{% endif %}
class="sr-only peer">
<div class="text-center text-sm py-2 px-1 rounded-lg border-2 border-gray-200
peer-checked:border-orange-500 peer-checked:bg-orange-50 peer-checked:text-orange-700
hover:border-gray-300 transition">
{{ vdata.name }}
</div>
{% if entry and entry.motor_vehicle_id == vid %}checked{% endif %}>
<span class="pill-inner" style="font-size:0.72rem;">{{ vdata.name }}</span>
</label>
{% endfor %}
</div>
</div>
<!-- Plages horaires -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Plages horaires</label>
<label class="field-label">Plages horaires</label>
<div id="time-slots" class="space-y-2">
{% if entry and entry.time_slots %}
{% for slot in entry.time_slots %}
<div class="flex gap-2 items-center time-slot-row">
<input type="time" name="start_time" value="{{ slot.start_time.strftime('%H:%M') }}"
class="flex-1 border rounded-lg px-3 py-2 text-sm">
<span class="text-gray-400"></span>
class="field-input flex-1" style="padding:0.5rem 0.6rem; font-family:'JetBrains Mono',monospace; font-size:0.9rem;">
<span style="color:var(--mist); font-size:1rem;"></span>
<input type="time" name="end_time" value="{{ slot.end_time.strftime('%H:%M') }}"
class="flex-1 border rounded-lg px-3 py-2 text-sm">
class="field-input flex-1" style="padding:0.5rem 0.6rem; font-family:'JetBrains Mono',monospace; font-size:0.9rem;">
<button type="button" onclick="this.closest('.time-slot-row').remove()"
class="text-red-400 hover:text-red-600 text-xl leading-none">×</button>
class="text-lg leading-none transition-colors" style="color:var(--mist);"
onmouseover="this.style.color='var(--rust)'" onmouseout="this.style.color='var(--mist)'">×</button>
</div>
{% endfor %}
{% else %}
<div class="flex gap-2 items-center time-slot-row">
<input type="time" name="start_time" class="flex-1 border rounded-lg px-3 py-2 text-sm">
<span class="text-gray-400"></span>
<input type="time" name="end_time" class="flex-1 border rounded-lg px-3 py-2 text-sm">
<input type="time" name="start_time"
class="field-input flex-1" style="padding:0.5rem 0.6rem; font-family:'JetBrains Mono',monospace; font-size:0.9rem;">
<span style="color:var(--mist); font-size:1rem;"></span>
<input type="time" name="end_time"
class="field-input flex-1" style="padding:0.5rem 0.6rem; font-family:'JetBrains Mono',monospace; font-size:0.9rem;">
<button type="button" onclick="this.closest('.time-slot-row').remove()"
class="text-red-400 hover:text-red-600 text-xl leading-none">×</button>
class="text-lg leading-none" style="color:var(--mist);"
onmouseover="this.style.color='var(--rust)'" onmouseout="this.style.color='var(--mist)'">×</button>
</div>
{% endif %}
</div>
<button type="button" onclick="addTimeSlot()"
class="mt-2 text-sm text-blue-600 hover:underline">+ Ajouter une plage</button>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Commentaire</label>
<textarea name="comment" rows="2"
class="w-full border rounded-lg px-3 py-2 text-sm"
placeholder="Formation, déplacement exceptionnel...">{{ entry.comment if entry and entry.comment else '' }}</textarea>
</div>
<div class="flex gap-3 pt-2">
<button type="submit"
class="flex-1 bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 transition">
Enregistrer
class="mt-2 text-xs font-semibold" style="color:var(--amber); letter-spacing:0.05em;">
+ Ajouter une plage
</button>
<a href="/" class="flex-1 text-center bg-gray-100 text-gray-700 py-3 rounded-lg font-semibold hover:bg-gray-200 transition">
Annuler
</a>
</div>
<!-- Commentaire -->
<div>
<label class="field-label">Commentaire</label>
<textarea name="comment" rows="2" class="field-input"
placeholder="Formation, déplacement exceptionnel…">{{ entry.comment if entry and entry.comment else '' }}</textarea>
</div>
<!-- Actions -->
<div class="flex gap-3 pt-1">
<button type="submit" class="btn-primary flex-1">Enregistrer</button>
<a href="/" class="btn-ghost flex-1">Annuler</a>
</div>
</form>
@@ -145,11 +144,14 @@ function addTimeSlot() {
const row = document.createElement('div');
row.className = 'flex gap-2 items-center time-slot-row';
row.innerHTML = `
<input type="time" name="start_time" class="flex-1 border rounded-lg px-3 py-2 text-sm">
<span class="text-gray-400">→</span>
<input type="time" name="end_time" class="flex-1 border rounded-lg px-3 py-2 text-sm">
<input type="time" name="start_time"
class="field-input flex-1" style="padding:0.5rem 0.6rem;font-family:'JetBrains Mono',monospace;font-size:0.9rem;">
<span style="color:var(--mist);font-size:1rem;">→</span>
<input type="time" name="end_time"
class="field-input flex-1" style="padding:0.5rem 0.6rem;font-family:'JetBrains Mono',monospace;font-size:0.9rem;">
<button type="button" onclick="this.closest('.time-slot-row').remove()"
class="text-red-400 hover:text-red-600 text-xl leading-none">×</button>
class="text-lg leading-none" style="color:var(--mist);"
onmouseover="this.style.color='var(--rust)'" onmouseout="this.style.color='var(--mist)'">×</button>
`;
container.appendChild(row);
}

View File

@@ -2,34 +2,59 @@
{% block title %}Historique{% endblock %}
{% block content %}
<h1 class="text-xl font-bold mb-4">Historique</h1>
<div class="flex items-baseline justify-between mb-5">
<p class="font-display text-2xl font-semibold" style="color:var(--ink);">Historique</p>
<a href="/entries/new" class="text-xs font-semibold" style="color:var(--amber); letter-spacing:0.05em;">+ Saisir</a>
</div>
{% if not entries %}
<p class="text-gray-500 text-center py-8">Aucune entrée pour le moment.</p>
<div class="card card-mist text-center py-8">
<p style="color:#9A9288; font-size:0.875rem;">Aucune entrée pour le moment.</p>
</div>
{% endif %}
<div class="space-y-2">
{% for entry in entries %}
<div class="bg-white rounded-xl shadow p-3 flex items-center justify-between">
<div>
<p class="font-medium text-sm">{{ entry.date | date_fr }}</p>
<p class="text-xs text-gray-500">
{{ entry.day_type }}
{% if entry.time_slots %} — {{ entry.total_hours_str() }}{% endif %}
{% if entry.journey_profile_id %} — {{ entry.journey_profile_id }}{% endif %}
</p>
{% if entry.comment %}
<p class="text-xs text-gray-400 mt-0.5 italic">{{ entry.comment }}</p>
{% set dot_color =
'#4A7A54' if entry.day_type == 'WORK' else
'#3A6AAA' if entry.day_type == 'TT' else
'#C8842A' if entry.day_type in ('GARDE', 'ASTREINTE') else
'#7A4AAA' if entry.day_type in ('RTT', 'FORMATION') else
'#B85035' if entry.day_type == 'MALADE' else
'#8A8278'
%}
<div class="card" style="border-left-color: {{ dot_color }}; padding: 0.75rem 1rem;">
<div class="flex items-start justify-between gap-2">
<div class="flex-1 min-w-0">
<p class="font-semibold text-sm" style="color:var(--ink);">{{ entry.date | date_fr }}</p>
<div class="flex items-center gap-2 mt-0.5">
<span class="text-xs font-semibold" style="color:{{ dot_color }}; letter-spacing:0.06em;">{{ entry.day_type }}</span>
{% if entry.time_slots %}
<span class="font-data text-xs" style="color:#8A8278;">{{ entry.total_hours_str() }}</span>
{% endif %}
{% if entry.motor_vehicle_id %}
<span class="text-xs" style="color:#9A9288;">· {{ entry.motor_vehicle_id }}</span>
{% elif entry.journey_profile_id %}
<span class="text-xs" style="color:#9A9288;">· {{ entry.journey_profile_id }}</span>
{% endif %}
</div>
<div class="flex gap-2">
<a href="/entries/{{ entry.id }}/edit" class="text-blue-500 text-sm hover:underline">Éditer</a>
{% if entry.comment %}
<p class="text-xs italic mt-1 truncate" style="color:#9A9288;">{{ entry.comment }}</p>
{% endif %}
</div>
<div class="flex items-center gap-3 flex-shrink-0">
<a href="/entries/{{ entry.id }}/edit"
class="text-xs font-semibold" style="color:var(--amber);">Éditer</a>
<form method="POST" action="/entries/{{ entry.id }}/delete"
onsubmit="return confirm('Supprimer cette entrée ?')">
<button class="text-red-400 text-sm hover:underline">Suppr.</button>
<button class="text-xs" style="color:var(--mist);"
onmouseover="this.style.color='var(--rust)'" onmouseout="this.style.color='var(--mist)'"></button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>

View File

@@ -1,12 +1,12 @@
{% extends "base.html" %}
{% block title %}Rapports{% endblock %}
{% block title %}Rapports {{ year }}{% endblock %}
{% block content %}
<div class="flex items-center justify-between mb-4">
<h1 class="text-xl font-bold">Rapports {{ year }}</h1>
<!-- En-tête -->
<div class="flex items-baseline justify-between mb-5">
<p class="font-display text-2xl font-semibold" style="color:var(--ink);">Rapports</p>
<form method="GET">
<select name="year" onchange="this.form.submit()"
class="border rounded-lg px-2 py-1 text-sm">
<select name="year" onchange="this.form.submit()" class="field-input" style="width:auto; padding:0.3rem 0.6rem; font-size:0.8rem;">
{% for y in range(2024, year + 2) %}
<option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
@@ -14,31 +14,48 @@
</form>
</div>
<div class="bg-white rounded-xl shadow p-4 mb-4">
<h2 class="font-semibold text-gray-700 mb-3">Kilométrage annuel</h2>
<!-- Kilométrage -->
<div class="card card-ink">
<p class="card-label">Kilométrage {{ year }}</p>
{% if total_km %}
<div class="space-y-3 mt-1">
{% for vehicle_id, km in total_km.items() %}
<div class="flex justify-between py-1 border-b border-gray-100 last:border-0">
<span class="text-sm text-gray-600">{{ vehicles.get(vehicle_id, {}).get('name', vehicle_id) }}</span>
<span class="font-medium text-sm">{{ km }} km</span>
<div class="flex items-baseline justify-between">
<span class="text-xs" style="color:#8A8278;">{{ vehicles.get(vehicle_id, {}).get('name', vehicle_id) }}</span>
<span class="font-data font-semibold" style="font-size:1.35rem; color:var(--ink);">
{{ km }}<span class="font-normal text-xs ml-1" style="color:#9A9288;">km</span>
</span>
</div>
{% endfor %}
<div class="flex justify-between pt-2 mt-1">
<span class="text-sm text-gray-500">CO₂ total</span>
<span class="font-medium text-sm">{{ total_co2_kg }} kg</span>
<div class="flex items-baseline justify-between pt-2" style="border-top:1px solid var(--parchment);">
<span class="text-xs font-semibold" style="color:#8A8278; letter-spacing:0.06em;">CO₂ TOTAL</span>
<span class="font-data font-semibold" style="font-size:1.35rem; color:var(--ink);">
{{ total_co2_kg }}<span class="font-normal text-xs ml-1" style="color:#9A9288;">kg</span>
</span>
</div>
</div>
{% else %}
<p class="mt-1 text-sm" style="color:#9A9288;">Aucun déplacement enregistré pour {{ year }}.</p>
{% endif %}
</div>
<div class="bg-white rounded-xl shadow p-4 mb-4">
<h2 class="font-semibold text-gray-700 mb-1">Frais réels (barème {{ year }})</h2>
<p class="text-xs text-gray-400 mb-3">Déduction fiscale estimée — véhicules motorisés uniquement</p>
<!-- Frais réels -->
<div class="card card-sage">
<p class="card-label">Frais réels — barème {{ year }}</p>
<p class="text-xs mb-3" style="color:#9A9288;">Déduction fiscale estimée · véhicules motorisés uniquement</p>
{% if frais_reels %}
<div class="space-y-3">
{% for vehicle_id, montant in frais_reels.items() %}
<div class="flex justify-between py-1 border-b border-gray-100 last:border-0">
<span class="text-sm text-gray-600">{{ vehicles.get(vehicle_id, {}).get('name', vehicle_id) }}</span>
<span class="font-bold text-green-700">{{ montant }} €</span>
<div class="flex items-baseline justify-between">
<span class="text-xs" style="color:#8A8278;">{{ vehicles.get(vehicle_id, {}).get('name', vehicle_id) }}</span>
<span class="font-data font-bold" style="font-size:1.6rem; color:var(--sage);">
{{ montant }}<span class="font-normal text-sm ml-1" style="color:#9A9288;"></span>
</span>
</div>
{% endfor %}
{% if not frais_reels %}
<p class="text-sm text-gray-400">Aucune donnée pour {{ year }}.</p>
</div>
{% else %}
<p class="text-sm" style="color:#9A9288;">Aucune donnée pour {{ year }}.</p>
{% endif %}
</div>

View File

@@ -7,7 +7,7 @@ import sqlalchemy as sa
def test_dashboard_empty(client):
response = client.get("/")
assert response.status_code == 200
assert "Tableau de bord" in response.text
assert "Journal de bord" in response.text
def test_entry_form_get(client):