Files
kleiderboerse/app/templates/_felder.html
T
StefanandClaude Opus 5 75b95b38e6 Phase 4: Oberfläche mit Galerie, Erfassung und Reservierung
Jinja2 mit HTMX, Tailwind über die Standalone-Binärdatei im Docker-Build -
kein Node.js, kein npm. Mobile-First, Tippflächen mindestens 44 px hoch.

Seiten: Galerie mit Filterleiste, Detailseite, Erfassungsformular,
Stapel-Import mit Entwurfsliste, Anmeldung. HTMX tauscht beim Filtern nur
die Liste aus statt die ganze Seite neu zu laden.

Aus den Entscheidungen im Plan folgt konkret:

- Der Stapel-Import steht ZUERST im Formular: wer zwanzig Teile hat, will
  nicht zwanzigmal ein Formular ausfüllen. Bewusst ohne capture-Attribut,
  damit sich wahlweise fotografieren oder aus der Mediathek wählen lässt -
  mit capture ginge nur je ein Foto pro Vorgang.
- Grösse, Kategorie, Geschlecht, Saison und Zustand werden vom zuletzt
  gespeicherten Teil übernommen. Beim Abarbeiten einer Kiste sind das fünf
  Felder weniger pro Stück.
- Nach dem Nachtragen eines Entwurfs geht es direkt zum nächsten, nicht auf
  die Detailseite - das ist der eigentliche Arbeitsablauf.
- Titel bleibt leer erlaubt; angezeigt wird dann Kategorie und Grösse.
- reserved_by erscheint öffentlich nur als "reserviert". Den Namen sieht
  ausschliesslich der angemeldete Betreiber.
- Nach dem Reservieren wird der Freigabe-Link angezeigt, als vollständige
  Adresse zum Aufbewahren.
- Post/Redirect/Get überall, damit ein Neuladen nicht erneut abschickt.
- CSRF-Token in jedem Formular, zusätzlich zu SameSite=Lax.

HTMX liegt lokal im Repository, nicht vom CDN: die CSP erlaubt nur
script-src 'self', und ein CDN-Aufruf gäbe die IP jedes Besuchers weiter -
dieselbe Überlegung wie bei Swagger und bei den Schriften der Kantone-App.
Ein Test prüft für alle Seiten, dass nichts von aussen geladen wird.

Nebenbei TemplateResponse auf die aktuelle Signatur umgestellt (64
Deprecation-Warnungen weniger).

77 Tests, alle grün. Zusätzlich der ganze Weg gegen den Container:
anmelden, drei Fotos als Stapel, Entwurf nachtragen, Werte werden für das
nächste Teil übernommen, als Gast reservieren, Name bleibt verborgen,
Selbst-Freigabe über den Link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
2026-08-29 23:29:56 +02:00

82 lines
3.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{# Die Eingabefelder eines Kleidungsstücks. Geteilt zwischen Erfassen und
Bearbeiten, damit beide Formulare nicht auseinanderlaufen.
`vorgabe` ist beim Erfassen leer und beim Bearbeiten das Kleidungsstück;
`zuletzt` sind die Werte des zuletzt gespeicherten Teils - beim
Abarbeiten einer Kiste ändert sich meist nur der Titel. #}
<div class="grid grid-cols-2 gap-3">
<div class="col-span-2">
<label class="etikett" for="title">Titel <span class="font-normal text-slate-400">(freiwillig)</span></label>
<input class="feld" id="title" name="title" maxlength="100"
value="{{ vorgabe.title or '' if vorgabe else '' }}"
placeholder="{{ platzhalter_titel }}">
<p class="text-xs text-slate-500 mt-1">
Leer lassen genügt dann steht dort automatisch Kategorie und Grösse.
</p>
</div>
<div>
<label class="etikett" for="size">Grösse</label>
<select class="feld" id="size" name="size" required>
<option value="">bitte wählen</option>
{% for g in groessen %}
<option value="{{ g.label }}"
{{ 'selected' if (vorgabe.size if vorgabe else zuletzt.size) == g.label }}>
{{ g.label }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="etikett" for="category_id">Kategorie</label>
<select class="feld" id="category_id" name="category_id" required>
<option value="">bitte wählen</option>
{% for k in kategorien %}
<option value="{{ k.id }}"
{{ 'selected' if (vorgabe.category_id if vorgabe else zuletzt.category_id)|string == k.id|string }}>
{{ k.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="etikett" for="gender">Für</label>
<select class="feld" id="gender" name="gender">
{% for wert, text in [('unisex','egal'), ('girl','Mädchen'), ('boy','Buben')] %}
<option value="{{ wert }}"
{{ 'selected' if (vorgabe.gender if vorgabe else zuletzt.gender) == wert }}>
{{ text }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="etikett" for="season">Saison</label>
<select class="feld" id="season" name="season">
{% for wert, text in [('all_year','ganzjährig'), ('spring_summer','Frühling/Sommer'), ('autumn_winter','Herbst/Winter')] %}
<option value="{{ wert }}"
{{ 'selected' if (vorgabe.season if vorgabe else zuletzt.season) == wert }}>
{{ text }}</option>
{% endfor %}
</select>
</div>
<div class="col-span-2">
<label class="etikett" for="condition">Zustand</label>
<select class="feld" id="condition" name="condition">
{% for wert, text in [('good','gut'), ('new','neu'), ('very_good','sehr gut'), ('worn','getragen')] %}
<option value="{{ wert }}"
{{ 'selected' if (vorgabe.condition if vorgabe else zuletzt.condition) == wert }}>
{{ text }}</option>
{% endfor %}
</select>
</div>
<div class="col-span-2">
<label class="etikett" for="description">Bemerkung <span class="font-normal text-slate-400">(freiwillig)</span></label>
<textarea class="feld py-2" id="description" name="description" rows="2"
placeholder="z.B. kleiner Fleck am Ärmel">{{ vorgabe.description or '' if vorgabe else '' }}</textarea>
</div>
</div>