From 124b3f61b69252c06ac90c8646ee738d4e12ddd4 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Thu, 23 Apr 2026 09:31:49 +0200 Subject: [PATCH] Plan: Inline Filters (pill-as-dropdown) implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-by-task plan for the design at 30d0991. 6 tasks, 1 checkpoint after Task 4 (all pill interactions demoable, modal still as fallback). Tasks: 1. Backend: project_team_pairs_json context + 2 tests 2. Template: popover shells + Apply button + json_script embeds 3. CSS: pill-editable, pill-dirty, popover, apply-group, toast (~150 lines) 4. JS: pill-popover interactive module (~300 lines, scoped IIFE) --- CHECKPOINT 1 --- 5. Retire modal: delete _report_config_modal.html, update index + report templates, keep backend context keys (still used by pill markup) 6. QA + shipped note Scope: ~480 LOC net added (not the ~330 estimated — JS came out larger once written with proper state management + cross-filter). Tests grow 42 -> 44. One new CDN-loaded library? No — Choices.js already loaded from Executive Report v2. Zero model changes, zero migrations. Noted trade-off in Task 5: selected_project_ids / selected_team_ids context keys were KEPT despite design doc suggesting removal — the pill popovers still use them for pre-selection + URL-diff init. Only the modal markup was retired. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/plans/2026-04-23-inline-filters-plan.md | 1439 ++++++++++++++++++ 1 file changed, 1439 insertions(+) create mode 100644 docs/plans/2026-04-23-inline-filters-plan.md 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 `