From d879e2268b8097601a67badfe8f433cb8bf6238f Mon Sep 17 00:00:00 2001 From: jedarden Date: Thu, 9 Apr 2026 14:31:41 -0400 Subject: [PATCH] feat: add search and filter to timeline with category checkboxes Implement comprehensive filter bar with: - Type filter checkboxes for event categories (Presence, Zones, Alerts, System, Learning) - Person and zone dropdowns for filtering - Date range selector with preset options (Today/Last 7 days/Last 30 days/Custom) - Text search input for fuzzy matching on descriptions - Client-side filtering for loaded events (instant feedback) - Server-side filtering for date-range queries - Load more pagination works for 500+ results Backend changes: - Add support for 'since'/'until' date range parameters in /api/events - Add zone_id and person_id query parameter aliases - Add POST /api/events/{id}/feedback endpoint for feedback submission Co-Authored-By: Claude Opus 4.6 --- dashboard/css/timeline.css | 184 ++++++- dashboard/index.html | 82 ++- dashboard/js/timeline.js | 681 +++++++++++++++++++++---- mothership/internal/api/events.go | 132 ++++- mothership/internal/api/events_test.go | 181 +++++++ mothership/internal/api/feedback.go | 56 ++ 6 files changed, 1197 insertions(+), 119 deletions(-) diff --git a/dashboard/css/timeline.css b/dashboard/css/timeline.css index 3b17bcf..8cadbac 100644 --- a/dashboard/css/timeline.css +++ b/dashboard/css/timeline.css @@ -20,7 +20,7 @@ /* Header */ .timeline-header { - padding: 16px; + padding: 12px 16px; background: var(--bg-secondary, #16162a); border-bottom: 1px solid var(--border-color, #2a2a4a); display: flex; @@ -31,7 +31,7 @@ } .timeline-title { - font-size: 18px; + font-size: 16px; font-weight: 600; color: var(--text-primary, #e0e0e0); margin: 0; @@ -41,12 +41,129 @@ } .timeline-title svg { - width: 20px; - height: 20px; + width: 18px; + height: 18px; color: var(--accent-color, #4a9eff); } +/* Filter Toggle Button */ +.timeline-filter-toggle { + background: transparent; + border: 1px solid var(--border-color, #2a2a4a); + border-radius: 4px; + padding: 6px 10px; + cursor: pointer; + color: var(--text-secondary, #a0a0b0); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.timeline-filter-toggle:hover { + background: var(--bg-primary, #121225); + color: var(--text-primary, #e0e0e0); + border-color: var(--accent-color, #4a9eff); +} + +.timeline-filter-toggle svg { + width: 14px; + height: 14px; +} + /* Filter Bar */ +.timeline-filter-bar { + padding: 12px 16px; + background: var(--bg-secondary, #16162a); + border-bottom: 1px solid var(--border-color, #2a2a4a); + display: flex; + gap: 24px; + flex-wrap: wrap; + max-height: 200px; + overflow-y: auto; + transition: max-height 0.3s ease; +} + +.timeline-filter-bar.collapsed { + max-height: 0; + padding-top: 0; + padding-bottom: 0; + border-bottom: none; + overflow: hidden; +} + +.timeline-filter-section { + flex: 1; + min-width: 200px; +} + +.timeline-filter-section-title { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 1px; + color: var(--text-muted, #888); + margin: 0 0 8px 0; +} + +/* Category Checkboxes */ +.timeline-category-checkboxes { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.timeline-category-checkbox { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + background: var(--bg-primary, #121225); + border: 1px solid var(--border-color, #2a2a4a); + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + font-size: 12px; + color: var(--text-secondary, #a0a0b0); +} + +.timeline-category-checkbox:hover { + border-color: var(--accent-color, #4a9eff); + background: var(--bg-tertiary, #1a1a2e); +} + +.timeline-category-checkbox input[type="checkbox"] { + width: 14px; + height: 14px; + accent-color: var(--accent-color, #4a9eff); + cursor: pointer; +} + +.timeline-category-checkbox:has(input:checked) { + border-color: var(--accent-color, #4a9eff); + background: rgba(74, 158, 255, 0.1); + color: var(--text-primary, #e0e0e0); +} + +.timeline-category-checkbox input[type="checkbox"]:disabled { + accent-color: var(--text-muted, #666); + opacity: 0.5; +} + +.timeline-category-label { + font-weight: 500; +} + +.timeline-category-icon { + font-size: 14px; +} + +/* Filter Controls */ +.timeline-filter-controls { + display: flex; + flex-direction: column; + gap: 8px; +} + .timeline-filters { display: flex; align-items: center; @@ -535,6 +652,15 @@ align-items: stretch; } + .timeline-filter-bar { + flex-direction: column; + gap: 12px; + } + + .timeline-filter-section { + min-width: unset; + } + .timeline-filters { flex-direction: column; align-items: stretch; @@ -554,3 +680,53 @@ flex-wrap: wrap; } } + +/* Custom Date Range */ +.timeline-custom-date { + display: flex; + align-items: center; + gap: 8px; + margin-top: 8px; + padding: 8px; + background: var(--bg-primary, #121225); + border-radius: 4px; +} + +.timeline-date-input { + background: var(--bg-secondary, #16162a); + border: 1px solid var(--border-color, #2a2a4a); + border-radius: 4px; + color: var(--text-primary, #e0e0e0); + padding: 4px 8px; + font-size: 12px; + font-family: inherit; + flex: 1; +} + +.timeline-date-input::-webkit-calendar-picker-indicator { + filter: invert(0.5); + cursor: pointer; +} + +.timeline-date-separator { + color: var(--text-muted, #888); + font-size: 12px; + padding: 0 4px; +} + +.timeline-date-apply-btn { + background: var(--accent-color, #4a9eff); + color: var(--bg-primary, #121225); + border: none; + border-radius: 4px; + padding: 4px 12px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; + white-space: nowrap; +} + +.timeline-date-apply-btn:hover { + background: #3a8ee6; +} diff --git a/dashboard/index.html b/dashboard/index.html index e1c43ea..ee89250 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -2687,23 +2687,71 @@ Timeline -
- - - - - + +
+
+
+

Categories

+
+ + + + + +
+
+
+

Filters

+
+ + + + + +
+