feat(filter): Berlin-Bezirk filter in Einstellungen

Adds a collapsible 12-Bezirk checkbox list to the filter tab. The UI
speaks Bezirke; internally the match runs on the PLZ extracted from
flat.address and resolved to a dominant Bezirk via a curated 187-PLZ
map (berlin_districts.py).

- Migration 0011 adds user_filters.districts (CSV of selected names)
- Empty stored value = no filter = all Bezirke ticked in the UI.
  Submitting "all ticked" or "none ticked" both normalise to empty
  so the defaults and the nuclear state mean the same thing.
- When a Bezirk filter is active, flats with an unknown/unmapped PLZ
  are excluded — if the user bothered to narrow by district, sneaking
  in unplaceable flats would be the wrong default.
- Filter summary on the Wohnungen page shows "N Bezirke" so it's
  visible the filter is active.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
EiSiMo 2026-04-23 10:05:55 +02:00
parent abd614604f
commit d13f9c5b6e
7 changed files with 202 additions and 20 deletions

View file

@ -43,6 +43,30 @@
{% endfor %}
</select>
</div>
<div class="col-span-2 md:col-span-3">
<details class="border border-soft rounded p-3"
{% if selected_districts|length != all_districts|length %}open{% endif %}>
<summary class="cursor-pointer font-medium text-sm select-none">
Stadtteile
<span class="text-slate-500 text-xs ml-1">
{% if selected_districts|length == all_districts|length %}alle{% else %}{{ selected_districts|length }} / {{ all_districts|length }}{% endif %}
</span>
</summary>
<div class="grid grid-cols-2 md:grid-cols-3 gap-2 mt-3">
{% for d in all_districts %}
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" name="districts" value="{{ d }}"
{% if d in selected_districts %}checked{% endif %}>
<span>{{ d }}</span>
</label>
{% endfor %}
</div>
<p class="text-xs text-slate-500 mt-2">
Standard: alle angekreuzt. Wohnungen ohne erkennbare Berliner PLZ werden
ausgeblendet, sobald du mindestens einen Haken entfernst.
</p>
</details>
</div>
<div class="col-span-2 md:col-span-3">
<button class="btn btn-primary" type="submit">Filter speichern</button>
</div>