From e931de1d3bcfc7ca6bab12753e7cba438910e823 Mon Sep 17 00:00:00 2001 From: Markus Zimmerberger Date: Fri, 11 Sep 2026 13:30:54 +0200 Subject: [PATCH] 1. transport_mode depends on origin and 2. pickup timetable --- app.py | 1 + models.py | 4 ++ static/stops.js | 86 ++++++++++++++++++++++++++++++++++++++++ templates/add_entry.html | 27 ++++++++++++- templates/base.html | 13 +++++- templates/dashboard.html | 5 ++- 6 files changed, 131 insertions(+), 5 deletions(-) diff --git a/app.py b/app.py index 3628e74..2b4ac63 100644 --- a/app.py +++ b/app.py @@ -203,6 +203,7 @@ def register_routes(app): def fill_trip_from_form(fahrt): fahrt.transport_mode = request.form["transport_mode"] fahrt.line = request.form.get("line", "").strip() + fahrt.course = request.form.get("course", "").strip() fahrt.origin = request.form["origin"].strip() fahrt.origin_stop_id = resolve_stop_id(request.form.get("origin_stop_id")) fahrt.destination = request.form["destination"].strip() diff --git a/models.py b/models.py index e9e1d08..48a4009 100644 --- a/models.py +++ b/models.py @@ -76,6 +76,10 @@ class Trip(db.Model): transport_mode = db.Column(db.String(50), nullable=False) line = db.Column(db.String(50)) + # optional detail beyond the line, e.g. direction/departure time looked up + # in an external journey planner ("Kurs"); free text, nothing to match. + # Text, not String(n) - see the "lines" column truncation incident. + course = db.Column(db.Text) # free-text label as entered; *_stop_id links to a known stop when matched origin = db.Column(db.String(120), nullable=False) origin_stop_id = db.Column(db.Integer, db.ForeignKey("stops.id")) diff --git a/static/stops.js b/static/stops.js index 2ff57c6..90cdf43 100644 --- a/static/stops.js +++ b/static/stops.js @@ -111,6 +111,9 @@ if (!hidden) return; function clearMatch() { + delete hidden.dataset.stopType; + delete hidden.dataset.stopLat; + delete hidden.dataset.stopLon; if (!hidden.value) return; hidden.value = ""; if (hint) { hint.textContent = ""; hint.classList.remove("matched"); } @@ -118,6 +121,9 @@ } function markMatch(stop) { hidden.value = String(stop.id); + hidden.dataset.stopType = stop.type; + hidden.dataset.stopLat = stop.lat; + hidden.dataset.stopLon = stop.lon; if (hint) { hint.textContent = "✓ " + stop.name + (stop.municipality ? ", " + stop.municipality : ""); @@ -223,8 +229,88 @@ }); } + // Which "Verkehrsmittel" options make sense for a given origin stop type. + // Rail stops aren't narrowed further (OSM doesn't tell S-Bahn from Fernzug). + var MODES_FOR_STOP_TYPE = { + bus: ["Bus"], + bus_station: ["Bus"], + tram: ["Straßenbahn"], + subway: ["U-Bahn"], + train: ["S-Bahn", "Regionalzug", "Fernzug (ÖBB/Railjet/WESTbahn)"], + }; + + function setupModeFilter(form) { + var select = form.querySelector("#transport_mode"); + var originHidden = form.querySelector("#origin_stop_id"); + if (!select || !originHidden) return; + var hint = form.querySelector("[data-mode-hint]"); + + function apply() { + var allowed = MODES_FOR_STOP_TYPE[originHidden.dataset.stopType || ""] || null; + var firstVisible = null; + Array.prototype.forEach.call(select.options, function (opt) { + var ok = !allowed || allowed.indexOf(opt.value) !== -1; + opt.hidden = !ok; + opt.disabled = !ok; + if (ok && !firstVisible) firstVisible = opt; + }); + if (allowed && firstVisible && allowed.indexOf(select.value) === -1) { + select.value = firstVisible.value; + } + if (hint) { + hint.textContent = allowed + ? "Eingeschränkt auf das Verkehrsmittel der gewählten Haltestelle." + : ""; + } + } + + form.addEventListener("stopchange", apply); + apply(); // e.g. an already-matched origin when editing a trip + } + + // "Fahrplan öffnen": opens Google Maps transit directions for the chosen + // stops (exact coordinates when matched, else the typed name) so the user + // can look up the real departure and copy it into the "Kurs" field. + function setupTimetableLink(form) { + var link = form.querySelector("[data-timetable-link]"); + var originText = form.querySelector("#origin"); + var destText = form.querySelector("#destination"); + var originHidden = form.querySelector("#origin_stop_id"); + var destHidden = form.querySelector("#destination_stop_id"); + if (!link || !originText || !destText || !originHidden || !destHidden) return; + + function locationParam(textInput, hiddenInput) { + var lat = hiddenInput.dataset.stopLat, lon = hiddenInput.dataset.stopLon; + if (lat && lon) return lat + "," + lon; + var name = textInput.value.trim(); + return name ? name + ", Österreich" : ""; + } + + function apply() { + var origin = locationParam(originText, originHidden); + var destination = locationParam(destText, destHidden); + if (!origin || !destination) { + link.removeAttribute("href"); + link.setAttribute("aria-disabled", "true"); + return; + } + link.href = "https://www.google.com/maps/dir/?api=1" + + "&origin=" + encodeURIComponent(origin) + + "&destination=" + encodeURIComponent(destination) + + "&travelmode=transit"; + link.removeAttribute("aria-disabled"); + } + + form.addEventListener("stopchange", apply); + originText.addEventListener("input", apply); + destText.addEventListener("input", apply); + apply(); + } + document.querySelectorAll("form").forEach(function (form) { form.querySelectorAll("[data-stop-input]").forEach(function (i) { setupStopCombo(i, form); }); form.querySelectorAll("[data-line-input]").forEach(function (i) { setupLineCombo(i, form); }); + setupModeFilter(form); + setupTimetableLink(form); }); })(); diff --git a/templates/add_entry.html b/templates/add_entry.html index 71859fa..a01fffa 100644 --- a/templates/add_entry.html +++ b/templates/add_entry.html @@ -11,6 +11,7 @@ {% endfor %} +

@@ -20,7 +21,12 @@ value="{{ fahrt.origin if fahrt else '' }}" data-stop-input data-stop-target="#origin_stop_id"> + value="{{ fahrt.origin_stop_id if fahrt and fahrt.origin_stop_id else '' }}" + {% if fahrt and fahrt.origin_stop %} + data-stop-type="{{ fahrt.origin_stop.stop_type }}" + data-stop-lat="{{ fahrt.origin_stop.latitude }}" + data-stop-lon="{{ fahrt.origin_stop.longitude }}" + {% endif %}>

{{ "✓ " ~ fahrt.origin_stop.name ~ (", " ~ fahrt.origin_stop.municipality if fahrt.origin_stop.municipality else "") if fahrt and fahrt.origin_stop else "" }}

@@ -33,7 +39,12 @@ value="{{ fahrt.destination if fahrt else '' }}" data-stop-input data-stop-target="#destination_stop_id"> + value="{{ fahrt.destination_stop_id if fahrt and fahrt.destination_stop_id else '' }}" + {% if fahrt and fahrt.destination_stop %} + data-stop-type="{{ fahrt.destination_stop.stop_type }}" + data-stop-lat="{{ fahrt.destination_stop.latitude }}" + data-stop-lon="{{ fahrt.destination_stop.longitude }}" + {% endif %}>

{{ "✓ " ~ fahrt.destination_stop.name ~ (", " ~ fahrt.destination_stop.municipality if fahrt.destination_stop.municipality else "") if fahrt and fahrt.destination_stop else "" }}

@@ -47,6 +58,18 @@

Halte oben wählen für Linienvorschläge.

+
+ + + + 🕒 Fahrplan öffnen + +

Öffnet Google Maps (Öffis) für die gewählten Halte – dort den genauen Kurs + ablesen und hier eintragen.

+
+
diff --git a/templates/base.html b/templates/base.html index d6ab238..9cf623a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -67,6 +67,7 @@ display: block; } label .required { color: var(--danger); margin-left: 2px; } + label .field-optional { color: var(--text-muted); font-weight: 400; margin-left: 2px; } .label-row { display: flex; justify-content: space-between; align-items: baseline; } .label-row a { font-size: 0.8rem; } @@ -101,12 +102,20 @@ width: 100%; } button:hover, .btn:hover { background: var(--accent-hover); } - button.secondary { + .secondary { background: transparent; border: 1px solid var(--panel-border); color: var(--text); } - button.secondary:hover { background: rgba(255,255,255,0.04); } + .secondary:hover { background: rgba(255,255,255,0.04); } + a.btn, a.btn:hover { display: inline-block; text-align: center; text-decoration: none; } + .btn.small { + width: auto; + padding: 0.45rem 0.8rem; + font-size: 0.85rem; + font-weight: 500; + } + .btn[aria-disabled="true"] { opacity: 0.5; pointer-events: none; } .divider { display: flex; diff --git a/templates/dashboard.html b/templates/dashboard.html index ca12289..ccf2451 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -25,7 +25,10 @@ {{ f.trip_date.strftime("%d.%m.%Y") }} {{ f.transport_mode }} - {{ f.line or "–" }} + + {{ f.line or "–" }} + {% if f.course %}
{{ f.course }}{% endif %} + {{ f.origin }}{% if f.origin_stop %}{% endif %} → {{ f.destination }}{% if f.destination_stop %}{% endif %}