ui: WBS dropdown, decimal-room filters, segmented toggle, 'Final absenden'
* Einstellungen → Profil: WBS-Typ jetzt <select> mit WBS 100/140/160/180/220 * Einstellungen → Filter: Zimmer min/max als number-Feld mit step=0.5 (2.5-Zimmer-Wohnungen sauber eingebbar) * Wohnungen-Top-Leiste: Segmented-Toggle (ein zusammenhängender Kippschalter) für die beiden Schalter, keine einzelnen Radio-Pills mehr * Trockenmodus umbenannt in 'Final absenden' (positive Polarität: An=echt senden). Bestätigungsdialog beim Einschalten. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
120d14e918
commit
d9468f6814
4 changed files with 73 additions and 42 deletions
|
|
@ -8,11 +8,15 @@
|
||||||
<input type="hidden" name="csrf" value="{{ csrf }}">
|
<input type="hidden" name="csrf" value="{{ csrf }}">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Zimmer min</label>
|
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Zimmer min</label>
|
||||||
<input class="input" name="rooms_min" value="{{ filters.rooms_min if filters.rooms_min is not none else '' }}">
|
<input class="input" type="number" step="0.5" min="0" max="20"
|
||||||
|
name="rooms_min" value="{{ filters.rooms_min if filters.rooms_min is not none else '' }}"
|
||||||
|
placeholder="z.B. 2 oder 2.5">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Zimmer max</label>
|
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">Zimmer max</label>
|
||||||
<input class="input" name="rooms_max" value="{{ filters.rooms_max if filters.rooms_max is not none else '' }}">
|
<input class="input" type="number" step="0.5" min="0" max="20"
|
||||||
|
name="rooms_max" value="{{ filters.rooms_max if filters.rooms_max is not none else '' }}"
|
||||||
|
placeholder="z.B. 3.5">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">max Miete (€)</label>
|
<label class="block text-xs uppercase tracking-wide text-slate-500 mb-1">max Miete (€)</label>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,12 @@
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs uppercase text-slate-500 mb-1">WBS-Typ</label>
|
<label class="block text-xs uppercase text-slate-500 mb-1">WBS-Typ</label>
|
||||||
<input class="input" name="wbs_type" value="{{ profile.wbs_type }}" placeholder="z.B. 180">
|
<select class="input" name="wbs_type">
|
||||||
|
<option value="0" {% if not profile.wbs_type or profile.wbs_type == '0' %}selected{% endif %}>—</option>
|
||||||
|
{% for t in ['100','140','160','180','220'] %}
|
||||||
|
<option value="{{ t }}" {% if profile.wbs_type|string == t %}selected{% endif %}>WBS {{ t }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs uppercase text-slate-500 mb-1">gültig bis</label>
|
<label class="block text-xs uppercase text-slate-500 mb-1">gültig bis</label>
|
||||||
|
|
@ -102,10 +107,10 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr class="my-6 border-soft">
|
<hr class="my-6 border-soft">
|
||||||
<h3 class="font-semibold mb-2">Trockenmodus</h3>
|
<h3 class="font-semibold mb-2">Final absenden</h3>
|
||||||
<p class="text-sm text-slate-600 mb-3">
|
<p class="text-sm text-slate-600 mb-3">
|
||||||
<span class="chip chip-warn">experimentell</span>
|
<span class="chip chip-warn">experimentell</span>
|
||||||
Im Trockenmodus wird das Formular ausgefüllt, aber nicht abgesendet. Erst deaktivieren,
|
Solange „Final absenden" aus ist, füllt die Automation das Formular aus, klickt aber
|
||||||
wenn du jeden Anbieter einmal im Trockenmodus erfolgreich getestet hast — den Schalter
|
nicht auf „Senden". Erst einschalten, wenn du jeden Anbieter einmal durchgetestet hast.
|
||||||
findest du auch oben auf der Wohnungen-Seite.
|
Der Schalter liegt auch oben auf der Wohnungen-Seite.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -19,51 +19,68 @@
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Reihe 2: Schalter Automatisch bewerben + Trockenmodus (Radio-Gruppen) -->
|
<!-- Reihe 2: Schalter Automatisch bewerben + Final absenden -->
|
||||||
<section class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<section class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
<!-- Automatisch bewerben -->
|
<!-- Automatisch bewerben -->
|
||||||
<form class="card p-4"
|
<form class="card p-4 flex items-center justify-between gap-3">
|
||||||
|
<input type="hidden" name="csrf" value="{{ csrf }}">
|
||||||
|
<div class="flex flex-col gap-0.5">
|
||||||
|
<div class="text-[11px] uppercase tracking-wide text-slate-500">Automatisch bewerben</div>
|
||||||
|
<div class="text-xs text-slate-500">bei Match ohne Nachfrage bewerben</div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle warn">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="value" value="off"
|
||||||
hx-post="/actions/auto-apply"
|
hx-post="/actions/auto-apply"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
|
hx-include="closest form"
|
||||||
hx-target="#wohnungen-body"
|
hx-target="#wohnungen-body"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML"
|
||||||
<input type="hidden" name="csrf" value="{{ csrf }}">
|
|
||||||
<div class="text-[11px] uppercase tracking-wide text-slate-500 mb-2">Automatisch bewerben</div>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<label class="radio-opt">
|
|
||||||
<input type="radio" name="value" value="off"
|
|
||||||
{% if not auto_apply_enabled %}checked{% endif %}>
|
{% if not auto_apply_enabled %}checked{% endif %}>
|
||||||
<span>Aus</span>
|
Aus
|
||||||
</label>
|
</label>
|
||||||
<label class="radio-opt"
|
<label>
|
||||||
{% if not auto_apply_enabled %}data-hx-confirm="Automatisches Bewerben einschalten? Bei jedem passenden Flat wird automatisch beworben."{% endif %}>
|
|
||||||
<input type="radio" name="value" value="on"
|
<input type="radio" name="value" value="on"
|
||||||
|
hx-post="/actions/auto-apply"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-include="closest form"
|
||||||
|
hx-target="#wohnungen-body"
|
||||||
|
hx-swap="outerHTML"
|
||||||
hx-confirm="Automatisches Bewerben einschalten? Bei jedem passenden Flat wird automatisch beworben."
|
hx-confirm="Automatisches Bewerben einschalten? Bei jedem passenden Flat wird automatisch beworben."
|
||||||
{% if auto_apply_enabled %}checked{% endif %}>
|
{% if auto_apply_enabled %}checked{% endif %}>
|
||||||
<span>An</span>
|
An
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Trockenmodus -->
|
<!-- Final absenden (inverse of submit_forms: on=real, off=trocken) -->
|
||||||
<form class="card p-4"
|
<form class="card p-4 flex items-center justify-between gap-3">
|
||||||
|
<input type="hidden" name="csrf" value="{{ csrf }}">
|
||||||
|
<div class="flex flex-col gap-0.5">
|
||||||
|
<div class="text-[11px] uppercase tracking-wide text-slate-500">Final absenden</div>
|
||||||
|
<div class="text-xs text-slate-500">aus = Formular ausfüllen, nicht abschicken</div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle warn">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="value" value="off"
|
||||||
hx-post="/actions/submit-forms"
|
hx-post="/actions/submit-forms"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
|
hx-include="closest form"
|
||||||
hx-target="#wohnungen-body"
|
hx-target="#wohnungen-body"
|
||||||
hx-swap="outerHTML">
|
hx-swap="outerHTML"
|
||||||
<input type="hidden" name="csrf" value="{{ csrf }}">
|
|
||||||
<div class="text-[11px] uppercase tracking-wide text-slate-500 mb-2">Trockenmodus</div>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<label class="radio-opt">
|
|
||||||
<input type="radio" name="value" value="on"
|
|
||||||
{% if not submit_forms %}checked{% endif %}>
|
{% if not submit_forms %}checked{% endif %}>
|
||||||
<span>An <span class="text-xs text-slate-500">(Formular ausfüllen, nicht absenden)</span></span>
|
Aus
|
||||||
</label>
|
</label>
|
||||||
<label class="radio-opt">
|
<label>
|
||||||
<input type="radio" name="value" value="off"
|
<input type="radio" name="value" value="on"
|
||||||
hx-confirm="Trockenmodus ausschalten? Formulare werden dann WIRKLICH abgesendet!"
|
hx-post="/actions/submit-forms"
|
||||||
|
hx-trigger="change"
|
||||||
|
hx-include="closest form"
|
||||||
|
hx-target="#wohnungen-body"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-confirm="Final absenden einschalten? Formulare werden dann WIRKLICH abgeschickt!"
|
||||||
{% if submit_forms %}checked{% endif %}>
|
{% if submit_forms %}checked{% endif %}>
|
||||||
<span>Aus <span class="text-xs text-[#b8404e]">(echt senden)</span></span>
|
An
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,17 @@
|
||||||
.chip-warn { background: #fff4dd; color: #a36a1f; border: 1px solid #f5d48b; }
|
.chip-warn { background: #fff4dd; color: #a36a1f; border: 1px solid #f5d48b; }
|
||||||
.chip-bad { background: #fde6e9; color: #b8404e; border: 1px solid #f5b5bf; }
|
.chip-bad { background: #fde6e9; color: #b8404e; border: 1px solid #f5b5bf; }
|
||||||
.chip-info { background: #e3effc; color: #1f5f99; border: 1px solid #b6d4f0; }
|
.chip-info { background: #e3effc; color: #1f5f99; border: 1px solid #b6d4f0; }
|
||||||
.radio-opt { display: inline-flex; align-items: center; gap: 0.45rem; cursor: pointer; padding: 0.35rem 0.65rem;
|
/* Segmented toggle (An/Aus Kippschalter) */
|
||||||
border: 1px solid var(--border); border-radius: 10px; background: var(--surface);
|
.toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 999px;
|
||||||
transition: border-color .15s, background .15s; user-select: none; }
|
overflow: hidden; background: var(--surface); font-size: 0.85rem; font-weight: 500; }
|
||||||
.radio-opt:has(input:checked) { border-color: var(--primary); background: #ecf4fc; box-shadow: 0 0 0 1px var(--primary) inset; }
|
.toggle label { padding: 0.45rem 1.1rem; cursor: pointer; user-select: none;
|
||||||
.radio-opt:hover { background: var(--ghost); }
|
color: var(--muted); transition: background .15s, color .15s; }
|
||||||
.radio-opt input[type="radio"] { accent-color: var(--primary); }
|
.toggle label + label { border-left: 1px solid var(--border); }
|
||||||
|
.toggle label input[type="radio"] { position: absolute; opacity: 0; pointer-events: none;
|
||||||
|
width: 0; height: 0; }
|
||||||
|
.toggle label:hover { color: var(--text); background: var(--ghost); }
|
||||||
|
.toggle label:has(input:checked) { background: var(--primary); color: #fff; }
|
||||||
|
.toggle.warn label:has(input[value="on"]:checked) { background: var(--danger); }
|
||||||
.brand-dot {
|
.brand-dot {
|
||||||
width: 2rem; height: 2rem; border-radius: 10px;
|
width: 2rem; height: 2rem; border-radius: 10px;
|
||||||
background: linear-gradient(135deg, #66b7f2 0%, #2f8ae0 60%, #fbd76b 100%);
|
background: linear-gradient(135deg, #66b7f2 0%, #2f8ae0 60%, #fbd76b 100%);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue