feat: motor vehicle selector in entry form and routes

This commit is contained in:
2026-03-11 17:52:13 +01:00
parent 9a37bc444d
commit ffbbf2da44
4 changed files with 46 additions and 6 deletions

View File

@@ -37,7 +37,7 @@ def index():
month_km = {} month_km = {}
month_co2 = 0.0 month_co2 = 0.0
for entry in month_entries: for entry in month_entries:
km = compute_km_for_entry(entry.journey_profile_id, journeys) km = compute_km_for_entry(entry.journey_profile_id, journeys, entry.motor_vehicle_id)
for v, d in km.items(): for v, d in km.items():
month_km[v] = month_km.get(v, 0) + d month_km[v] = month_km.get(v, 0) + d
month_co2 += compute_co2_grams(km, vehicles) month_co2 += compute_co2_grams(km, vehicles)

View File

@@ -3,7 +3,7 @@ from datetime import date, time
import sqlalchemy as sa import sqlalchemy as sa
from app import db from app import db
from app.models import WorkEntry, TimeSlot from app.models import WorkEntry, TimeSlot
from app.config_loader import get_journeys, day_types_without_journey from app.config_loader import get_journeys, get_motor_vehicles, day_types_without_journey, journey_has_motor
bp = Blueprint("entries", __name__, url_prefix="/entries") bp = Blueprint("entries", __name__, url_prefix="/entries")
@@ -42,6 +42,9 @@ def entry_form(entry_id=None):
entry_date = date.fromisoformat(request.form["date"]) entry_date = date.fromisoformat(request.form["date"])
day_type = request.form["day_type"] day_type = request.form["day_type"]
journey_profile_id = request.form.get("journey_profile_id") or None journey_profile_id = request.form.get("journey_profile_id") or None
motor_vehicle_id = request.form.get("motor_vehicle_id") or None
if not journey_has_motor(journey_profile_id):
motor_vehicle_id = None
comment = request.form.get("comment") or None comment = request.form.get("comment") or None
if day_type in day_types_without_journey(): if day_type in day_types_without_journey():
@@ -60,6 +63,7 @@ def entry_form(entry_id=None):
entry.day_type = day_type entry.day_type = day_type
entry.journey_profile_id = journey_profile_id entry.journey_profile_id = journey_profile_id
entry.comment = comment entry.comment = comment
entry.motor_vehicle_id = motor_vehicle_id
for slot in list(entry.time_slots): for slot in list(entry.time_slots):
db.session.delete(slot) db.session.delete(slot)
@@ -84,6 +88,7 @@ def entry_form(entry_id=None):
entry=entry, entry=entry,
day_types=DAY_TYPES, day_types=DAY_TYPES,
journeys=journeys, journeys=journeys,
motor_vehicles=get_motor_vehicles(),
day_types_without_journey=day_types_without_journey(), day_types_without_journey=day_types_without_journey(),
today=date.today().isoformat(), today=date.today().isoformat(),
) )

View File

@@ -25,7 +25,7 @@ def index():
total_km = {} total_km = {}
total_co2 = 0.0 total_co2 = 0.0
for entry in entries: for entry in entries:
km = compute_km_for_entry(entry.journey_profile_id, journeys) km = compute_km_for_entry(entry.journey_profile_id, journeys, entry.motor_vehicle_id)
for v, d in km.items(): for v, d in km.items():
total_km[v] = total_km.get(v, 0) + d total_km[v] = total_km.get(v, 0) + d
total_co2 += compute_co2_grams(km, vehicles) total_co2 += compute_co2_grams(km, vehicles)

View File

@@ -37,11 +37,13 @@
<div id="journey-section" <div id="journey-section"
class="{% if entry and entry.day_type in day_types_without_journey %}hidden{% endif %}"> 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="block text-sm font-medium text-gray-700 mb-1">Trajet domicile-travail</label>
<select name="journey_profile_id" <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="w-full border rounded-lg px-3 py-2 text-sm">
<option value="">— Pas de déplacement —</option> <option value="">— Pas de déplacement —</option>
{% for jid, jdata in journeys.items() %} {% for jid, jdata in journeys.items() %}
<option value="{{ jid }}" <option value="{{ jid }}"
data-has-motor="{{ 'true' if 'moteur' in jdata.distances else 'false' }}"
{% if entry and entry.journey_profile_id == jid %}selected{% endif %}> {% if entry and entry.journey_profile_id == jid %}selected{% endif %}>
{{ jdata.name }} {{ jdata.name }}
({% for v, d in jdata.distances.items() %}{{ d }} km {{ v }}{% if not loop.last %} + {% endif %}{% endfor %}) ({% for v, d in jdata.distances.items() %}{{ d }} km {{ v }}{% if not loop.last %} + {% endif %}{% endfor %})
@@ -50,6 +52,24 @@
</select> </select>
</div> </div>
<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>
<div class="grid grid-cols-2 gap-2">
{% for vid, vdata in motor_vehicles.items() %}
<label class="cursor-pointer">
<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>
</label>
{% endfor %}
</div>
</div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-2">Plages horaires</label> <label class="block text-sm font-medium text-gray-700 mb-2">Plages horaires</label>
<div id="time-slots" class="space-y-2"> <div id="time-slots" class="space-y-2">
@@ -101,8 +121,23 @@
const NO_JOURNEY_TYPES = {{ day_types_without_journey | list | tojson }}; const NO_JOURNEY_TYPES = {{ day_types_without_journey | list | tojson }};
function updateJourneyVisibility(dayType) { function updateJourneyVisibility(dayType) {
const section = document.getElementById('journey-section'); const journeySection = document.getElementById('journey-section');
section.classList.toggle('hidden', NO_JOURNEY_TYPES.includes(dayType)); const hidden = NO_JOURNEY_TYPES.includes(dayType);
journeySection.classList.toggle('hidden', hidden);
if (hidden) {
updateMotorVehicleVisibility('');
} else {
const select = document.getElementById('journey_profile_id');
if (select) updateMotorVehicleVisibility(select.value);
}
}
function updateMotorVehicleVisibility(journeyId) {
const section = document.getElementById('motor-vehicle-section');
const select = document.getElementById('journey_profile_id');
const selectedOption = select ? select.querySelector(`option[value="${journeyId}"]`) : null;
const hasMotor = selectedOption && selectedOption.dataset.hasMotor === 'true';
section.classList.toggle('hidden', !hasMotor);
} }
function addTimeSlot() { function addTimeSlot() {