From c6ec47a8fc90bba2c32176c9aae19876593c2761 Mon Sep 17 00:00:00 2001 From: josh Date: Wed, 22 Apr 2026 22:52:13 -0400 Subject: [PATCH] Replace status tabs with multi-select checkbox dropdown, default to Open + In Progress Status filtering now supports selecting multiple statuses via a dropdown with checkboxes. Backend updated to accept comma-separated status values using Prisma `in` operator. Co-Authored-By: Claude Opus 4.6 --- client/src/pages/tickets/TicketFilters.tsx | 72 +++++++++++++--------- client/src/pages/tickets/Tickets.tsx | 6 +- server/src/services/searchService.ts | 9 ++- server/src/services/ticketService.ts | 7 ++- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/client/src/pages/tickets/TicketFilters.tsx b/client/src/pages/tickets/TicketFilters.tsx index 31222cf..fbefdac 100644 --- a/client/src/pages/tickets/TicketFilters.tsx +++ b/client/src/pages/tickets/TicketFilters.tsx @@ -1,7 +1,9 @@ -import { Trash2, Save } from 'lucide-react'; +import { Trash2, Save, Check } from 'lucide-react'; import CTISelect from '../../components/CTISelect'; -import type { TicketStatus, User } from '../../types'; +import { TICKET_STATUSES } from '../../../../shared/schemas/enums'; +import type { User } from '../../types'; import type { SavedView } from '../../../../shared/types'; +import { STATUS_LABELS } from '../../../../shared/constants/labels'; import { DropdownMenu, DropdownMenuContent, @@ -13,16 +15,8 @@ import { import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; -const STATUS_TABS: { value: TicketStatus | ''; label: string }[] = [ - { value: '', label: 'All' }, - { value: 'OPEN', label: 'Open' }, - { value: 'IN_PROGRESS', label: 'In progress' }, - { value: 'RESOLVED', label: 'Resolved' }, - { value: 'CLOSED', label: 'Closed' }, -]; - interface TicketFiltersProps { - status: TicketStatus | ''; + status: string; severity: string; assigneeId: string; categoryId: string; @@ -64,25 +58,22 @@ export default function TicketFilters({ total, isFetching, }: TicketFiltersProps) { + const selectedStatuses = status ? status.split(',') : []; + + const toggleStatus = (s: string) => { + const next = selectedStatuses.includes(s) + ? selectedStatuses.filter((v) => v !== s) + : [...selectedStatuses, s]; + onUpdateParam('status', next.length > 0 ? next.join(',') : null); + }; + + const statusLabel = + selectedStatuses.length === 0 || selectedStatuses.length === TICKET_STATUSES.length + ? 'All statuses' + : selectedStatuses.map((s) => STATUS_LABELS[s] ?? s).join(', '); + return ( <> - {/* Status tabs */} -
- {STATUS_TABS.map((tab) => ( - - ))} -
- {/* Row 1: Search + saved views + result count */}
+ + + + + + {TICKET_STATUSES.map((s) => ( + { + e.preventDefault(); + toggleStatus(s); + }} + className="gap-2" + > +
+ {selectedStatuses.includes(s) && } +
+ {STATUS_LABELS[s] ?? s} +
+ ))} +
+
+