Replace status tabs with multi-select checkbox dropdown, default to Open + In Progress
Build & Push / Test (client) (push) Successful in 29s
Build & Push / Test (server) (push) Successful in 26s
Build & Push / Build Client (push) Successful in 1m9s
Build & Push / Build Server (push) Successful in 1m17s

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 <noreply@anthropic.com>
This commit is contained in:
2026-04-22 22:52:13 -04:00
parent cfe7ad56ff
commit c6ec47a8fc
4 changed files with 61 additions and 33 deletions
+44 -28
View File
@@ -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 */}
<div className="flex items-center border-b border-border mb-4 -mx-4 px-4 overflow-x-auto">
{STATUS_TABS.map((tab) => (
<button
key={tab.value}
onClick={() => onUpdateParam('status', tab.value || null)}
className={`px-3 py-2 text-sm whitespace-nowrap border-b-2 -mb-px transition-colors ${
status === tab.value
? 'border-primary text-foreground font-medium'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Row 1: Search + saved views + result count */}
<div className="flex gap-2 mb-2 items-center">
<input
@@ -151,6 +142,31 @@ export default function TicketFilters({
{/* Row 2: Filter selectors */}
<div className="flex gap-2 mb-4 items-center flex-wrap">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="gap-1.5 text-sm font-normal">
{statusLabel}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
{TICKET_STATUSES.map((s) => (
<DropdownMenuItem
key={s}
onSelect={(e) => {
e.preventDefault();
toggleStatus(s);
}}
className="gap-2"
>
<div className="flex h-4 w-4 items-center justify-center rounded-sm border border-input">
{selectedStatuses.includes(s) && <Check size={12} />}
</div>
{STATUS_LABELS[s] ?? s}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<select
value={severity}
onChange={(e) => onUpdateParam('severity', e.target.value || null)}
+3 -3
View File
@@ -4,7 +4,6 @@ import { useShortcut } from '../../hooks/useShortcuts';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { toast } from 'sonner';
import Layout from '../../components/Layout';
import { TicketStatus } from '../../types';
import { useAuth } from '../../contexts/AuthContext';
import {
useTicketsPaged,
@@ -44,7 +43,8 @@ export default function Tickets() {
const { user: authUser } = useAuth();
const { data: users = [] } = useUsers();
const status = (params.get('status') ?? '') as TicketStatus | '';
const DEFAULT_STATUSES = 'OPEN,IN_PROGRESS';
const status = params.get('status') ?? DEFAULT_STATUSES;
const severity = params.get('severity') ?? '';
const assigneeId = params.get('assigneeId') ?? '';
const categoryId = params.get('categoryId') ?? '';
@@ -88,7 +88,7 @@ export default function Tickets() {
};
const queryParams = {
status: status || undefined,
status: status || undefined as string | undefined,
severity: severity ? Number(severity) : undefined,
assigneeId: assigneeId || undefined,
categoryId: categoryId || undefined,