diff --git a/docs/plans/2026-04-23-inline-filters-plan.md b/docs/plans/2026-04-23-inline-filters-plan.md new file mode 100644 index 0000000..e5b4eff --- /dev/null +++ b/docs/plans/2026-04-23-inline-filters-plan.md @@ -0,0 +1,1439 @@ +# Inline Filters (Pill-as-Dropdown) Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Replace the modal-based filter form on `/report/` with interactive pill-dropdowns (Apply button appears only when pending changes exist, bidirectional project↔team cross-filter auto-removes invalid selections), and retire `_report_config_modal.html` entirely. + +**Architecture:** Template-only change except for one backend tweak. The existing filter-pill strip (shipped with Executive Report v2) becomes clickable — each pill opens a Bootstrap-like popover containing the relevant editable widget (date picker or Choices.js multi-select). A scoped IIFE JS module inside `report.html` manages popover state, tracks dirty filters, and rebuilds the submit URL on Apply. Cross-filter data comes from a single JSON ` +``` + +**Step 4.3: Check syntax** + +```bash +USE_SQLITE=true DJANGO_DEBUG=true python manage.py check +``` + +Expected: only the `staticfiles.W004` warning. + +**Step 4.4: Commit** + +```bash +git add core/templates/core/report.html +git commit -m "$(cat <<'EOF' +JS: pill-popover interactive module for inline filters + +One scoped IIFE that wires up the three filter pills into an +interactive, state-managed UI: + +- Click pill -> open popover (lazy-inits Choices.js on first open) +- Click outside / Esc / other pill -> close +- OK commits popover's local edits into pending state; dirty pills + get orange outline + pulsing dot; Apply button slides in at the + right end of the strip +- Cross-filter: picking projects auto-removes now-invalid teams + with toast notice ("Removed Team X — no logs on selected projects"), + and vice versa. Scope = entire history. +- Apply -> rebuilds querystring from pending state + navigates + (full page reload, same URL scheme as the retired modal) +- Reset -> reverts all pills to URL-current values + +XSS-safe throughout: textContent and createElement; no innerHTML with +user data. Matches the pattern in base.html's work-log-payroll modal +from the previous feature. + +Graceful fallback: if Choices.js CDN fails to load, the module bails +early with a console warning; native pre-selection: stringify the IDs so + # the template's `{% if p.id|stringformat:"s" in selected_project_ids %}` + # comparison works (Django templates compare strings to strings). + context['selected_project_ids'] = [str(p) for p in (project_ids or [])] + context['selected_team_ids'] = [str(t) for t in (team_ids or [])] +``` + +**Keep** the `projects` / `teams` context keys (the pills' popovers still use them to render `