1. transport_mode depends on origin and 2. pickup timetable
Deploy PBT / deploy (push) Successful in 16s
Deploy PBT / deploy (push) Successful in 16s
This commit is contained in:
@@ -203,6 +203,7 @@ def register_routes(app):
|
|||||||
def fill_trip_from_form(fahrt):
|
def fill_trip_from_form(fahrt):
|
||||||
fahrt.transport_mode = request.form["transport_mode"]
|
fahrt.transport_mode = request.form["transport_mode"]
|
||||||
fahrt.line = request.form.get("line", "").strip()
|
fahrt.line = request.form.get("line", "").strip()
|
||||||
|
fahrt.course = request.form.get("course", "").strip()
|
||||||
fahrt.origin = request.form["origin"].strip()
|
fahrt.origin = request.form["origin"].strip()
|
||||||
fahrt.origin_stop_id = resolve_stop_id(request.form.get("origin_stop_id"))
|
fahrt.origin_stop_id = resolve_stop_id(request.form.get("origin_stop_id"))
|
||||||
fahrt.destination = request.form["destination"].strip()
|
fahrt.destination = request.form["destination"].strip()
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ class Trip(db.Model):
|
|||||||
|
|
||||||
transport_mode = db.Column(db.String(50), nullable=False)
|
transport_mode = db.Column(db.String(50), nullable=False)
|
||||||
line = db.Column(db.String(50))
|
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
|
# free-text label as entered; *_stop_id links to a known stop when matched
|
||||||
origin = db.Column(db.String(120), nullable=False)
|
origin = db.Column(db.String(120), nullable=False)
|
||||||
origin_stop_id = db.Column(db.Integer, db.ForeignKey("stops.id"))
|
origin_stop_id = db.Column(db.Integer, db.ForeignKey("stops.id"))
|
||||||
|
|||||||
@@ -111,6 +111,9 @@
|
|||||||
if (!hidden) return;
|
if (!hidden) return;
|
||||||
|
|
||||||
function clearMatch() {
|
function clearMatch() {
|
||||||
|
delete hidden.dataset.stopType;
|
||||||
|
delete hidden.dataset.stopLat;
|
||||||
|
delete hidden.dataset.stopLon;
|
||||||
if (!hidden.value) return;
|
if (!hidden.value) return;
|
||||||
hidden.value = "";
|
hidden.value = "";
|
||||||
if (hint) { hint.textContent = ""; hint.classList.remove("matched"); }
|
if (hint) { hint.textContent = ""; hint.classList.remove("matched"); }
|
||||||
@@ -118,6 +121,9 @@
|
|||||||
}
|
}
|
||||||
function markMatch(stop) {
|
function markMatch(stop) {
|
||||||
hidden.value = String(stop.id);
|
hidden.value = String(stop.id);
|
||||||
|
hidden.dataset.stopType = stop.type;
|
||||||
|
hidden.dataset.stopLat = stop.lat;
|
||||||
|
hidden.dataset.stopLon = stop.lon;
|
||||||
if (hint) {
|
if (hint) {
|
||||||
hint.textContent = "✓ " + stop.name +
|
hint.textContent = "✓ " + stop.name +
|
||||||
(stop.municipality ? ", " + stop.municipality : "");
|
(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) {
|
document.querySelectorAll("form").forEach(function (form) {
|
||||||
form.querySelectorAll("[data-stop-input]").forEach(function (i) { setupStopCombo(i, form); });
|
form.querySelectorAll("[data-stop-input]").forEach(function (i) { setupStopCombo(i, form); });
|
||||||
form.querySelectorAll("[data-line-input]").forEach(function (i) { setupLineCombo(i, form); });
|
form.querySelectorAll("[data-line-input]").forEach(function (i) { setupLineCombo(i, form); });
|
||||||
|
setupModeFilter(form);
|
||||||
|
setupTimetableLink(form);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<option value="{{ option }}" {{ "selected" if fahrt and fahrt.transport_mode == option }}>{{ option }}</option>
|
<option value="{{ option }}" {{ "selected" if fahrt and fahrt.transport_mode == option }}>{{ option }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<p class="field-hint" data-mode-hint></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field combo">
|
<div class="field combo">
|
||||||
@@ -20,7 +21,12 @@
|
|||||||
value="{{ fahrt.origin if fahrt else '' }}"
|
value="{{ fahrt.origin if fahrt else '' }}"
|
||||||
data-stop-input data-stop-target="#origin_stop_id">
|
data-stop-input data-stop-target="#origin_stop_id">
|
||||||
<input type="hidden" id="origin_stop_id" name="origin_stop_id"
|
<input type="hidden" id="origin_stop_id" name="origin_stop_id"
|
||||||
value="{{ fahrt.origin_stop_id if fahrt and fahrt.origin_stop_id else '' }}">
|
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 %}>
|
||||||
<p class="field-hint {{ 'matched' if fahrt and fahrt.origin_stop }}" data-stop-hint>{{
|
<p class="field-hint {{ 'matched' if fahrt and fahrt.origin_stop }}" data-stop-hint>{{
|
||||||
"✓ " ~ fahrt.origin_stop.name ~ (", " ~ fahrt.origin_stop.municipality if fahrt.origin_stop.municipality else "")
|
"✓ " ~ fahrt.origin_stop.name ~ (", " ~ fahrt.origin_stop.municipality if fahrt.origin_stop.municipality else "")
|
||||||
if fahrt and fahrt.origin_stop else "" }}</p>
|
if fahrt and fahrt.origin_stop else "" }}</p>
|
||||||
@@ -33,7 +39,12 @@
|
|||||||
value="{{ fahrt.destination if fahrt else '' }}"
|
value="{{ fahrt.destination if fahrt else '' }}"
|
||||||
data-stop-input data-stop-target="#destination_stop_id">
|
data-stop-input data-stop-target="#destination_stop_id">
|
||||||
<input type="hidden" id="destination_stop_id" name="destination_stop_id"
|
<input type="hidden" id="destination_stop_id" name="destination_stop_id"
|
||||||
value="{{ fahrt.destination_stop_id if fahrt and fahrt.destination_stop_id else '' }}">
|
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 %}>
|
||||||
<p class="field-hint {{ 'matched' if fahrt and fahrt.destination_stop }}" data-stop-hint>{{
|
<p class="field-hint {{ 'matched' if fahrt and fahrt.destination_stop }}" data-stop-hint>{{
|
||||||
"✓ " ~ fahrt.destination_stop.name ~ (", " ~ fahrt.destination_stop.municipality if fahrt.destination_stop.municipality else "")
|
"✓ " ~ fahrt.destination_stop.name ~ (", " ~ fahrt.destination_stop.municipality if fahrt.destination_stop.municipality else "")
|
||||||
if fahrt and fahrt.destination_stop else "" }}</p>
|
if fahrt and fahrt.destination_stop else "" }}</p>
|
||||||
@@ -47,6 +58,18 @@
|
|||||||
<p class="field-hint" data-line-hint>Halte oben wählen für Linienvorschläge.</p>
|
<p class="field-hint" data-line-hint>Halte oben wählen für Linienvorschläge.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="course">Kurs <span class="field-optional">(Richtung, Abfahrtszeit – optional)</span></label>
|
||||||
|
<input type="text" id="course" name="course" placeholder="z. B. Richtung Kleinzell, ab 14:32"
|
||||||
|
value="{{ fahrt.course if fahrt else '' }}">
|
||||||
|
<a class="btn secondary small" data-timetable-link target="_blank" rel="noopener"
|
||||||
|
style="margin-top: 0.5rem; align-self: flex-start;" aria-disabled="true">
|
||||||
|
🕒 Fahrplan öffnen
|
||||||
|
</a>
|
||||||
|
<p class="field-hint">Öffnet Google Maps (Öffis) für die gewählten Halte – dort den genauen Kurs
|
||||||
|
ablesen und hier eintragen.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="trip_date">Datum<span class="required">*</span></label>
|
<label for="trip_date">Datum<span class="required">*</span></label>
|
||||||
<input type="date" id="trip_date" name="trip_date" value="{{ heute }}" required>
|
<input type="date" id="trip_date" name="trip_date" value="{{ heute }}" required>
|
||||||
|
|||||||
+11
-2
@@ -67,6 +67,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
label .required { color: var(--danger); margin-left: 2px; }
|
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 { display: flex; justify-content: space-between; align-items: baseline; }
|
||||||
.label-row a { font-size: 0.8rem; }
|
.label-row a { font-size: 0.8rem; }
|
||||||
|
|
||||||
@@ -101,12 +102,20 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
button:hover, .btn:hover { background: var(--accent-hover); }
|
button:hover, .btn:hover { background: var(--accent-hover); }
|
||||||
button.secondary {
|
.secondary {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid var(--panel-border);
|
border: 1px solid var(--panel-border);
|
||||||
color: var(--text);
|
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 {
|
.divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ f.trip_date.strftime("%d.%m.%Y") }}</td>
|
<td>{{ f.trip_date.strftime("%d.%m.%Y") }}</td>
|
||||||
<td>{{ f.transport_mode }}</td>
|
<td>{{ f.transport_mode }}</td>
|
||||||
<td>{{ f.line or "–" }}</td>
|
<td>
|
||||||
|
{{ f.line or "–" }}
|
||||||
|
{% if f.course %}<br><span style="color: var(--text-muted); font-size: 0.8rem;">{{ f.course }}</span>{% endif %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ f.origin }}{% if f.origin_stop %}<span class="stop-dot" title="Verifizierte Haltestelle"> ●</span>{% endif %}
|
{{ f.origin }}{% if f.origin_stop %}<span class="stop-dot" title="Verifizierte Haltestelle"> ●</span>{% endif %}
|
||||||
→ {{ f.destination }}{% if f.destination_stop %}<span class="stop-dot" title="Verifizierte Haltestelle"> ●</span>{% endif %}
|
→ {{ f.destination }}{% if f.destination_stop %}<span class="stop-dot" title="Verifizierte Haltestelle"> ●</span>{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user