11import useTranslation from "next-translate/useTranslation" ;
22import { useRouter } from "next/router" ;
33import Loader from "react-spinners/ClipLoader" ;
4- import { useState } from "react" ;
4+ import { useState , useMemo } from "react" ;
55
66import { ContextMenu } from "@radix-ui/themes" ;
77import { getCookie } from "cookies-next" ;
@@ -10,7 +10,7 @@ import Link from "next/link";
1010import { useQuery } from "react-query" ;
1111import { useUser } from "../../store/session" ;
1212import { Popover , PopoverContent , PopoverTrigger } from "@/shadcn/ui/popover" ;
13- import { CheckIcon , PlusCircle } from "lucide-react" ;
13+ import { CheckIcon , Filter , PlusCircle , X } from "lucide-react" ;
1414import { Button } from "@/shadcn/ui/button" ;
1515import {
1616 Command ,
@@ -32,6 +32,28 @@ async function getUserTickets(token: any) {
3232 return res . json ( ) ;
3333}
3434
35+ // Add this new component for the filter badge
36+ const FilterBadge = ( {
37+ text,
38+ onRemove,
39+ } : {
40+ text : string ;
41+ onRemove : ( ) => void ;
42+ } ) => (
43+ < div className = "flex items-center gap-1 bg-accent rounded-md px-2 py-1 text-xs" >
44+ < span > { text } </ span >
45+ < button
46+ onClick = { ( e ) => {
47+ e . preventDefault ( ) ;
48+ onRemove ( ) ;
49+ } }
50+ className = "hover:bg-muted rounded-full p-0.5"
51+ >
52+ < X className = "h-3 w-3" />
53+ </ button >
54+ </ div >
55+ ) ;
56+
3557export default function Tickets ( ) {
3658 const router = useRouter ( ) ;
3759 const { t } = useTranslation ( "peppermint" ) ;
@@ -47,7 +69,10 @@ export default function Tickets() {
4769 const low = "bg-blue-100 text-blue-800" ;
4870 const normal = "bg-green-100 text-green-800" ;
4971
72+ const [ filterSelected , setFilterSelected ] = useState ( ) ;
5073 const [ selectedPriorities , setSelectedPriorities ] = useState < string [ ] > ( [ ] ) ;
74+ const [ selectedStatuses , setSelectedStatuses ] = useState < string [ ] > ( [ ] ) ;
75+ const [ selectedAssignees , setSelectedAssignees ] = useState < string [ ] > ( [ ] ) ;
5176
5277 const handlePriorityToggle = ( priority : string ) => {
5378 setSelectedPriorities ( ( prev ) =>
@@ -57,14 +82,65 @@ export default function Tickets() {
5782 ) ;
5883 } ;
5984
85+ const handleStatusToggle = ( status : string ) => {
86+ setSelectedStatuses ( ( prev ) =>
87+ prev . includes ( status )
88+ ? prev . filter ( ( s ) => s !== status )
89+ : [ ...prev , status ]
90+ ) ;
91+ } ;
92+
93+ const handleAssigneeToggle = ( assignee : string ) => {
94+ setSelectedAssignees ( ( prev ) =>
95+ prev . includes ( assignee )
96+ ? prev . filter ( ( a ) => a !== assignee )
97+ : [ ...prev , assignee ]
98+ ) ;
99+ } ;
100+
60101 const filteredTickets = data
61- ? data . tickets . filter ( ( ticket ) =>
62- selectedPriorities . length > 0
63- ? selectedPriorities . includes ( ticket . priority )
64- : true
65- )
102+ ? data . tickets . filter ( ( ticket ) => {
103+ const priorityMatch =
104+ selectedPriorities . length === 0 ||
105+ selectedPriorities . includes ( ticket . priority ) ;
106+ const statusMatch =
107+ selectedStatuses . length === 0 ||
108+ selectedStatuses . includes ( ticket . isComplete ? "closed" : "open" ) ;
109+ const assigneeMatch =
110+ selectedAssignees . length === 0 ||
111+ selectedAssignees . includes ( ticket . assignedTo ?. name || "Unassigned" ) ;
112+
113+ return priorityMatch && statusMatch && assigneeMatch ;
114+ } )
66115 : [ ] ;
67116
117+ type FilterType = "priority" | "status" | "assignee" | null ;
118+ const [ activeFilter , setActiveFilter ] = useState < FilterType > ( null ) ;
119+ const [ filterSearch , setFilterSearch ] = useState ( "" ) ;
120+
121+ const filteredPriorities = useMemo ( ( ) => {
122+ const priorities = [ "low" , "medium" , "high" ] ;
123+ return priorities . filter ( ( priority ) =>
124+ priority . toLowerCase ( ) . includes ( filterSearch . toLowerCase ( ) )
125+ ) ;
126+ } , [ filterSearch ] ) ;
127+
128+ const filteredStatuses = useMemo ( ( ) => {
129+ const statuses = [ "open" , "closed" ] ;
130+ return statuses . filter ( ( status ) =>
131+ status . toLowerCase ( ) . includes ( filterSearch . toLowerCase ( ) )
132+ ) ;
133+ } , [ filterSearch ] ) ;
134+
135+ const filteredAssignees = useMemo ( ( ) => {
136+ const assignees = data ?. tickets
137+ . map ( ( t ) => t . assignedTo ?. name || "Unassigned" )
138+ . filter ( ( name , index , self ) => self . indexOf ( name ) === index ) ;
139+ return assignees ?. filter ( ( assignee ) =>
140+ assignee . toLowerCase ( ) . includes ( filterSearch . toLowerCase ( ) )
141+ ) ;
142+ } , [ data ?. tickets , filterSearch ] ) ;
143+
68144 return (
69145 < div >
70146 { status === "loading" && (
@@ -76,60 +152,221 @@ export default function Tickets() {
76152 { status === "success" && (
77153 < div >
78154 < div className = "flex flex-col" >
79- < div className = "py-2 px-6 bg-gray-200 dark:bg-[#0A090C] border-b-[1px] flex flex-row items-center justify-between" >
80- < div className = "flex flex-row items-center space-x-4" >
81- < span className = "text-sm font-bold" > All Tickets</ span >
155+ < div className = "py-2 px-3 bg-background border-b-[1px] flex flex-row items-center justify-between" >
156+ < div className = "flex flex-row items-center gap-2" >
82157 < Popover >
83158 < PopoverTrigger asChild >
84159 < Button
85- variant = "outline "
160+ variant = "ghost "
86161 size = "sm"
87- className = "h-6 bg-transparent border-dashed "
162+ className = "h-6 bg-transparent"
88163 >
89- < PlusCircle className = "mr-2 h-4 w-4" />
90- Priority
164+ < Filter className = "mr-2 h-4 w-4" />
165+ < span className = "hidden sm:block" > Filters </ span >
91166 </ Button >
92167 </ PopoverTrigger >
93- < PopoverContent className = "w-[200px] p-0" align = "start" >
94- < Command >
95- < CommandInput placeholder = "Search priority..." />
96- < CommandList >
97- < CommandEmpty > No results found.</ CommandEmpty >
98- < CommandGroup >
99- { [ "low" , "medium" , "high" ] . map ( ( priority ) => (
168+ < PopoverContent className = "w-[300px] p-0" align = "start" >
169+ { ! activeFilter ? (
170+ < Command >
171+ < CommandInput placeholder = "Search filters..." />
172+ < CommandList >
173+ < CommandEmpty > No results found.</ CommandEmpty >
174+ < CommandGroup >
175+ < CommandItem
176+ onSelect = { ( ) => setActiveFilter ( "priority" ) }
177+ >
178+ Priority
179+ </ CommandItem >
180+ < CommandItem
181+ onSelect = { ( ) => setActiveFilter ( "status" ) }
182+ >
183+ Status
184+ </ CommandItem >
185+ < CommandItem
186+ onSelect = { ( ) => setActiveFilter ( "assignee" ) }
187+ >
188+ Assigned To
189+ </ CommandItem >
190+ </ CommandGroup >
191+ </ CommandList >
192+ </ Command >
193+ ) : activeFilter === "priority" ? (
194+ < Command >
195+ < CommandInput
196+ placeholder = "Search priority..."
197+ value = { filterSearch }
198+ onValueChange = { setFilterSearch }
199+ />
200+ < CommandList >
201+ < CommandEmpty > No priorities found.</ CommandEmpty >
202+ < CommandGroup heading = "Priority" >
203+ { filteredPriorities . map ( ( priority ) => (
204+ < CommandItem
205+ key = { priority }
206+ onSelect = { ( ) => handlePriorityToggle ( priority ) }
207+ >
208+ < div
209+ className = { cn (
210+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary" ,
211+ selectedPriorities . includes ( priority )
212+ ? "bg-primary text-primary-foreground"
213+ : "opacity-50 [&_svg]:invisible"
214+ ) }
215+ >
216+ < CheckIcon className = { cn ( "h-4 w-4" ) } />
217+ </ div >
218+ < span className = "capitalize" > { priority } </ span >
219+ </ CommandItem >
220+ ) ) }
221+ </ CommandGroup >
222+ < CommandSeparator />
223+ < CommandGroup >
100224 < CommandItem
101- key = { priority }
102- onSelect = { ( ) => handlePriorityToggle ( priority ) }
225+ onSelect = { ( ) => {
226+ setActiveFilter ( null ) ;
227+ setFilterSearch ( "" ) ;
228+ } }
229+ className = "justify-center text-center"
103230 >
104- < div
105- className = { cn (
106- "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary" ,
107- selectedPriorities . includes ( priority )
108- ? "bg-primary text-primary-foreground"
109- : "opacity-50 [&_svg]:invisible"
110- ) }
231+ Back to filters
232+ </ CommandItem >
233+ </ CommandGroup >
234+ </ CommandList >
235+ </ Command >
236+ ) : activeFilter === "status" ? (
237+ < Command >
238+ < CommandInput
239+ placeholder = "Search status..."
240+ value = { filterSearch }
241+ onValueChange = { setFilterSearch }
242+ />
243+ < CommandList >
244+ < CommandEmpty > No statuses found.</ CommandEmpty >
245+ < CommandGroup heading = "Status" >
246+ { filteredStatuses . map ( ( status ) => (
247+ < CommandItem
248+ key = { status }
249+ onSelect = { ( ) => handleStatusToggle ( status ) }
111250 >
112- < CheckIcon className = { cn ( "h-4 w-4" ) } />
113- </ div >
114- < span className = "capitalize" > { priority } </ span >
251+ < div
252+ className = { cn (
253+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary" ,
254+ selectedStatuses . includes ( status )
255+ ? "bg-primary text-primary-foreground"
256+ : "opacity-50 [&_svg]:invisible"
257+ ) }
258+ >
259+ < CheckIcon className = { cn ( "h-4 w-4" ) } />
260+ </ div >
261+ < span className = "capitalize" > { status } </ span >
262+ </ CommandItem >
263+ ) ) }
264+ </ CommandGroup >
265+ < CommandSeparator />
266+ < CommandGroup >
267+ < CommandItem
268+ onSelect = { ( ) => {
269+ setActiveFilter ( null ) ;
270+ setFilterSearch ( "" ) ;
271+ } }
272+ className = "justify-center text-center"
273+ >
274+ Back to filters
115275 </ CommandItem >
116- ) ) }
117- </ CommandGroup >
118- < >
276+ </ CommandGroup >
277+ </ CommandList >
278+ </ Command >
279+ ) : activeFilter === "assignee" ? (
280+ < Command >
281+ < CommandInput
282+ placeholder = "Search assignee..."
283+ value = { filterSearch }
284+ onValueChange = { setFilterSearch }
285+ />
286+ < CommandList >
287+ < CommandEmpty > No assignees found.</ CommandEmpty >
288+ < CommandGroup heading = "Assigned To" >
289+ { filteredAssignees ?. map ( ( name ) => (
290+ < CommandItem
291+ key = { name }
292+ onSelect = { ( ) => handleAssigneeToggle ( name ) }
293+ >
294+ < div
295+ className = { cn (
296+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary" ,
297+ selectedAssignees . includes ( name )
298+ ? "bg-primary text-primary-foreground"
299+ : "opacity-50 [&_svg]:invisible"
300+ ) }
301+ >
302+ < CheckIcon className = { cn ( "h-4 w-4" ) } />
303+ </ div >
304+ < span > { name } </ span >
305+ </ CommandItem >
306+ ) ) }
307+ </ CommandGroup >
119308 < CommandSeparator />
120309 < CommandGroup >
121310 < CommandItem
122- onSelect = { ( ) => setSelectedPriorities ( [ ] ) }
311+ onSelect = { ( ) => {
312+ setActiveFilter ( null ) ;
313+ setFilterSearch ( "" ) ;
314+ } }
123315 className = "justify-center text-center"
124316 >
125- Clear filters
317+ Back to filters
126318 </ CommandItem >
127319 </ CommandGroup >
128- </ >
129- </ CommandList >
130- </ Command >
320+ </ CommandList >
321+ </ Command >
322+ ) : null }
131323 </ PopoverContent >
132324 </ Popover >
325+
326+ { /* Display selected filters */ }
327+ < div className = "flex flex-wrap gap-2" >
328+ { selectedPriorities . map ( ( priority ) => (
329+ < FilterBadge
330+ key = { `priority-${ priority } ` }
331+ text = { `Priority: ${ priority } ` }
332+ onRemove = { ( ) => handlePriorityToggle ( priority ) }
333+ />
334+ ) ) }
335+
336+ { selectedStatuses . map ( ( status ) => (
337+ < FilterBadge
338+ key = { `status-${ status } ` }
339+ text = { `Status: ${ status } ` }
340+ onRemove = { ( ) => handleStatusToggle ( status ) }
341+ />
342+ ) ) }
343+
344+ { selectedAssignees . map ( ( assignee ) => (
345+ < FilterBadge
346+ key = { `assignee-${ assignee } ` }
347+ text = { `Assignee: ${ assignee } ` }
348+ onRemove = { ( ) => handleAssigneeToggle ( assignee ) }
349+ />
350+ ) ) }
351+
352+ { /* Clear all filters button - only show if there are filters */ }
353+ { ( selectedPriorities . length > 0 ||
354+ selectedStatuses . length > 0 ||
355+ selectedAssignees . length > 0 ) && (
356+ < Button
357+ variant = "ghost"
358+ size = "sm"
359+ className = "h-6 px-2 text-xs"
360+ onClick = { ( ) => {
361+ setSelectedPriorities ( [ ] ) ;
362+ setSelectedStatuses ( [ ] ) ;
363+ setSelectedAssignees ( [ ] ) ;
364+ } }
365+ >
366+ Clear all
367+ </ Button >
368+ ) }
369+ </ div >
133370 </ div >
134371 < div > </ div >
135372 </ div >
0 commit comments