From cfe7ad56ffef9d2885cdb5e1d2aebfb094eb37be Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 22 Apr 2026 22:20:29 -0400 Subject: [PATCH] Rework tickets filter bar into two-row layout with consistent CTI styling Split the dense single-row filter bar into two rows: search + saved views on top, filter selectors below. Fix CTI selectors to use design system tokens instead of hardcoded dark classes, and upgrade the saved views button with an icon and badge count. Co-Authored-By: Claude Opus 4.6 --- client/src/components/CTISelect.tsx | 57 +++++++++- client/src/pages/tickets/TicketFilters.tsx | 121 +++++++++++---------- 2 files changed, 117 insertions(+), 61 deletions(-) diff --git a/client/src/components/CTISelect.tsx b/client/src/components/CTISelect.tsx index b97c9d4..5fd76c7 100644 --- a/client/src/components/CTISelect.tsx +++ b/client/src/components/CTISelect.tsx @@ -4,9 +4,10 @@ interface CTISelectProps { value: { categoryId: string; typeId: string; itemId: string }; onChange: (value: { categoryId: string; typeId: string; itemId: string }) => void; disabled?: boolean; + compact?: boolean; } -export default function CTISelect({ value, onChange, disabled }: CTISelectProps) { +export default function CTISelect({ value, onChange, disabled, compact }: CTISelectProps) { const { data: categories = [] } = useCategories(); const { data: types = [] } = useTypes(value.categoryId || undefined); const { data: items = [] } = useItems(value.typeId || undefined); @@ -24,12 +25,58 @@ export default function CTISelect({ value, onChange, disabled }: CTISelectProps) }; const selectClass = - 'block w-full bg-gray-800 border border-gray-700 text-gray-100 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed'; + 'block w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50'; + + if (compact) { + return ( +
+ + + +
+ ); + } return (
- + handleType(e.target.value)} @@ -63,7 +110,7 @@ export default function CTISelect({ value, onChange, disabled }: CTISelectProps)
- + onSearchChange(e.target.value)} placeholder="Search title, overview, ID…" - className="flex-1 min-w-48 max-w-xs px-3 py-1.5 rounded-md border border-input bg-background text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-ring" + className="flex-1 min-w-48 max-w-sm px-3 py-1.5 rounded-md border border-input bg-background text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-ring" /> - - - - -
- { - onSetParams((prev) => { - const next = new URLSearchParams(prev); - next.delete('page'); - if (cti.categoryId) next.set('categoryId', cti.categoryId); - else next.delete('categoryId'); - if (cti.typeId) next.set('typeId', cti.typeId); - else next.delete('typeId'); - if (cti.itemId) next.set('itemId', cti.itemId); - else next.delete('itemId'); - return next; - }); - }} - /> -
- - {/* Saved views */} - + {savedViews.length > 0 && ( + + {savedViews.length} + + )} + Saved views @@ -191,6 +148,58 @@ export default function TicketFilters({ {isFetching ? 'Loading…' : `${total} result${total === 1 ? '' : 's'}`}
+ + {/* Row 2: Filter selectors */} +
+ + + + + { + onSetParams((prev) => { + const next = new URLSearchParams(prev); + next.delete('page'); + if (cti.categoryId) next.set('categoryId', cti.categoryId); + else next.delete('categoryId'); + if (cti.typeId) next.set('typeId', cti.typeId); + else next.delete('typeId'); + if (cti.itemId) next.set('itemId', cti.itemId); + else next.delete('itemId'); + return next; + }); + }} + /> +
); }