@@ -200,24 +200,25 @@ def register_routes(app):
|
||||
return None
|
||||
return stop.id if stop is not None else None
|
||||
|
||||
def fill_trip_from_form(fahrt):
|
||||
fahrt.transport_mode = request.form["transport_mode"]
|
||||
fahrt.line = request.form.get("line", "").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()
|
||||
fahrt.destination_stop_id = resolve_stop_id(
|
||||
request.form.get("destination_stop_id")
|
||||
)
|
||||
fahrt.trip_date = parse_trip_date(request.form.get("trip_date"))
|
||||
fahrt.rating = int(request.form["rating"])
|
||||
fahrt.comment = request.form.get("comment", "").strip()
|
||||
|
||||
@app.route("/fahrt/neu", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def neue_fahrt():
|
||||
if request.method == "POST":
|
||||
fahrt = Trip(
|
||||
user_id=current_user.id,
|
||||
transport_mode=request.form["transport_mode"],
|
||||
line=request.form.get("line", "").strip(),
|
||||
origin=request.form["origin"].strip(),
|
||||
origin_stop_id=resolve_stop_id(request.form.get("origin_stop_id")),
|
||||
destination=request.form["destination"].strip(),
|
||||
destination_stop_id=resolve_stop_id(
|
||||
request.form.get("destination_stop_id")
|
||||
),
|
||||
trip_date=parse_trip_date(request.form.get("trip_date")),
|
||||
rating=int(request.form["rating"]),
|
||||
comment=request.form.get("comment", "").strip(),
|
||||
)
|
||||
fahrt = Trip(user_id=current_user.id)
|
||||
fill_trip_from_form(fahrt)
|
||||
db.session.add(fahrt)
|
||||
db.session.commit()
|
||||
flash("Fahrt gespeichert.", "success")
|
||||
@@ -227,6 +228,28 @@ def register_routes(app):
|
||||
"add_entry.html",
|
||||
verkehrsmittel_optionen=VERKEHRSMITTEL_OPTIONEN,
|
||||
heute=date.today().isoformat(),
|
||||
fahrt=None,
|
||||
)
|
||||
|
||||
@app.route("/fahrt/<int:fahrt_id>/bearbeiten", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def fahrt_bearbeiten(fahrt_id):
|
||||
fahrt = Trip.query.get_or_404(fahrt_id)
|
||||
if fahrt.user_id != current_user.id:
|
||||
flash("Nicht erlaubt.", "error")
|
||||
return redirect(url_for("dashboard"))
|
||||
|
||||
if request.method == "POST":
|
||||
fill_trip_from_form(fahrt)
|
||||
db.session.commit()
|
||||
flash("Fahrt aktualisiert.", "success")
|
||||
return redirect(url_for("dashboard"))
|
||||
|
||||
return render_template(
|
||||
"add_entry.html",
|
||||
verkehrsmittel_optionen=VERKEHRSMITTEL_OPTIONEN,
|
||||
heute=fahrt.trip_date.isoformat(),
|
||||
fahrt=fahrt,
|
||||
)
|
||||
|
||||
@app.route("/fahrt/<int:fahrt_id>/loeschen", methods=["POST"])
|
||||
|
||||
+25
-15
@@ -1,14 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Neue Fahrt – PBT{% endblock %}
|
||||
{% block title %}{{ "Fahrt bearbeiten" if fahrt else "Neue Fahrt" }} – PBT{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h2>Neue Fahrt erfassen</h2>
|
||||
<form method="post">
|
||||
<h2>{{ "Fahrt bearbeiten" if fahrt else "Neue Fahrt erfassen" }}</h2>
|
||||
<form method="post" action="{{ url_for('fahrt_bearbeiten', fahrt_id=fahrt.id) if fahrt else url_for('neue_fahrt') }}">
|
||||
<div class="field">
|
||||
<label for="transport_mode">Verkehrsmittel<span class="required">*</span></label>
|
||||
<select id="transport_mode" name="transport_mode" required>
|
||||
{% for option in verkehrsmittel_optionen %}
|
||||
<option value="{{ option }}">{{ option }}</option>
|
||||
<option value="{{ option }}" {{ "selected" if fahrt and fahrt.transport_mode == option }}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
@@ -17,23 +17,32 @@
|
||||
<label for="origin">Von<span class="required">*</span></label>
|
||||
<input type="text" id="origin" name="origin" required
|
||||
placeholder="Haltestelle suchen …"
|
||||
value="{{ fahrt.origin if fahrt else '' }}"
|
||||
data-stop-input data-stop-target="#origin_stop_id">
|
||||
<input type="hidden" id="origin_stop_id" name="origin_stop_id">
|
||||
<p class="field-hint" data-stop-hint></p>
|
||||
<input type="hidden" id="origin_stop_id" name="origin_stop_id"
|
||||
value="{{ fahrt.origin_stop_id if fahrt and fahrt.origin_stop_id else '' }}">
|
||||
<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 "")
|
||||
if fahrt and fahrt.origin_stop else "" }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field combo">
|
||||
<label for="destination">Nach<span class="required">*</span></label>
|
||||
<input type="text" id="destination" name="destination" required
|
||||
placeholder="Haltestelle suchen …"
|
||||
value="{{ fahrt.destination if fahrt else '' }}"
|
||||
data-stop-input data-stop-target="#destination_stop_id">
|
||||
<input type="hidden" id="destination_stop_id" name="destination_stop_id">
|
||||
<p class="field-hint" data-stop-hint></p>
|
||||
<input type="hidden" id="destination_stop_id" name="destination_stop_id"
|
||||
value="{{ fahrt.destination_stop_id if fahrt and fahrt.destination_stop_id else '' }}">
|
||||
<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 "")
|
||||
if fahrt and fahrt.destination_stop else "" }}</p>
|
||||
</div>
|
||||
|
||||
<div class="field combo">
|
||||
<label for="line">Linie</label>
|
||||
<input type="text" id="line" name="line" placeholder="z.B. S3, 693"
|
||||
value="{{ fahrt.line if fahrt else '' }}"
|
||||
data-line-input>
|
||||
<p class="field-hint" data-line-hint>Halte oben wählen für Linienvorschläge.</p>
|
||||
</div>
|
||||
@@ -46,20 +55,21 @@
|
||||
<div class="field">
|
||||
<label for="rating">Bewertung<span class="required">*</span></label>
|
||||
<select id="rating" name="rating" required>
|
||||
<option value="5">★★★★★ Sehr gut</option>
|
||||
<option value="4">★★★★☆ Gut</option>
|
||||
<option value="3" selected>★★★☆☆ Okay</option>
|
||||
<option value="2">★★☆☆☆ Schlecht</option>
|
||||
<option value="1">★☆☆☆☆ Sehr schlecht</option>
|
||||
{% set current_rating = fahrt.rating if fahrt else 3 %}
|
||||
<option value="5" {{ "selected" if current_rating == 5 }}>★★★★★ Sehr gut</option>
|
||||
<option value="4" {{ "selected" if current_rating == 4 }}>★★★★☆ Gut</option>
|
||||
<option value="3" {{ "selected" if current_rating == 3 }}>★★★☆☆ Okay</option>
|
||||
<option value="2" {{ "selected" if current_rating == 2 }}>★★☆☆☆ Schlecht</option>
|
||||
<option value="1" {{ "selected" if current_rating == 1 }}>★☆☆☆☆ Sehr schlecht</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="comment">Kommentar</label>
|
||||
<textarea id="comment" name="comment" rows="3"></textarea>
|
||||
<textarea id="comment" name="comment" rows="3">{{ fahrt.comment if fahrt else "" }}</textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit">Speichern</button>
|
||||
<button type="submit">{{ "Änderungen speichern" if fahrt else "Speichern" }}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -136,6 +136,26 @@
|
||||
.stars { color: #e3b341; white-space: nowrap; }
|
||||
.stop-dot { color: var(--success); font-size: 0.7rem; vertical-align: middle; }
|
||||
|
||||
.row-actions { display: flex; gap: 0.4rem; }
|
||||
.row-actions form { display: contents; }
|
||||
.icon-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
border: 1px solid var(--panel-border);
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon-btn svg { width: 16px; height: 16px; }
|
||||
.icon-btn.edit:hover { color: var(--accent); border-color: var(--accent); background: rgba(74, 158, 255, 0.1); }
|
||||
.icon-btn.delete { color: var(--danger); }
|
||||
.icon-btn.delete:hover { background: rgba(248, 81, 73, 0.12); border-color: var(--danger); }
|
||||
|
||||
.combo { position: relative; display: flex; flex-direction: column; }
|
||||
.combo-list {
|
||||
position: absolute;
|
||||
|
||||
@@ -32,9 +32,28 @@
|
||||
</td>
|
||||
<td class="stars">{{ "★" * f.rating }}{{ "☆" * (5 - f.rating) }}</td>
|
||||
<td>
|
||||
<form method="post" action="{{ url_for('fahrt_loeschen', fahrt_id=f.id) }}" onsubmit="return confirm('Fahrt wirklich löschen?');" style="margin: 0;">
|
||||
<button type="submit" class="secondary" style="width: auto; padding: 0.35rem 0.7rem; font-size: 0.8rem;">Löschen</button>
|
||||
<div class="row-actions">
|
||||
<a class="icon-btn edit" href="{{ url_for('fahrt_bearbeiten', fahrt_id=f.id) }}"
|
||||
title="Fahrt bearbeiten" aria-label="Fahrt bearbeiten">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<form method="post" action="{{ url_for('fahrt_loeschen', fahrt_id=f.id) }}"
|
||||
onsubmit="return confirm('Fahrt wirklich löschen?');">
|
||||
<button type="submit" class="icon-btn delete" title="Fahrt löschen" aria-label="Fahrt löschen">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path>
|
||||
<path d="M10 11v6"></path>
|
||||
<path d="M14 11v6"></path>
|
||||
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% if f.comment %}
|
||||
|
||||
Reference in New Issue
Block a user