11'use client' ;
22
33import { useState , useEffect } from 'react' ;
4- import { getAllTickets , replyToTicket , closeTicket } from '@/actions/tickets' ;
5- import { MessageCircle , Send , CheckCircle2 , User , Clock , Loader2 , ChevronDown , ChevronUp } from 'lucide-react' ;
4+ import { getAllTickets , replyToTicket , closeTicket , getTicketStats } from '@/actions/tickets' ;
5+ import { MessageCircle , Send , CheckCircle2 , User , Loader2 } from 'lucide-react' ;
66import { Modal } from '@/components/common/Modal' ;
77import { Notification , NotificationType } from '@/components/common/Notification' ;
88
@@ -30,25 +30,52 @@ type Ticket = {
3030export default function AdminTicketsPage ( ) {
3131 const [ tickets , setTickets ] = useState < Ticket [ ] > ( [ ] ) ;
3232 const [ isLoading , setIsLoading ] = useState ( true ) ;
33+ const [ page , setPage ] = useState ( 1 ) ;
34+ const [ hasMore , setHasMore ] = useState ( false ) ;
35+ const [ stats , setStats ] = useState ( { OPEN : 0 , ANSWERED : 0 , CLOSED : 0 } ) ;
36+ const [ filter , setFilter ] = useState < 'OPEN' | 'ANSWERED' | 'CLOSED' | 'ALL' > ( 'OPEN' ) ;
37+
3338 const [ expandedTicketId , setExpandedTicketId ] = useState < string | null > ( null ) ;
3439 const [ replyText , setReplyText ] = useState < { [ key : string ] : string } > ( { } ) ;
3540 const [ isSubmitting , setIsSubmitting ] = useState < { [ key : string ] : boolean } > ( { } ) ;
36- const [ filter , setFilter ] = useState < 'OPEN' | 'ANSWERED' | 'CLOSED' | 'ALL' > ( 'OPEN' ) ;
3741
3842 // UI Feedback State
3943 const [ notification , setNotification ] = useState < { message : string ; type : NotificationType } | null > ( null ) ;
4044 const [ confirmCloseId , setConfirmCloseId ] = useState < string | null > ( null ) ;
4145 const [ postReplyActionId , setPostReplyActionId ] = useState < string | null > ( null ) ;
4246
4347 useEffect ( ( ) => {
44- fetchTickets ( ) ;
45- } , [ ] ) ;
48+ setPage ( 1 ) ;
49+ fetchTickets ( 1 , true ) ;
50+ fetchStats ( ) ;
51+ } , [ filter ] ) ;
52+
53+ const fetchStats = async ( ) => {
54+ const counts = await getTicketStats ( ) ;
55+ setStats ( counts ) ;
56+ } ;
4657
47- const fetchTickets = async ( ) => {
58+ const fetchTickets = async ( pageNum : number , isReset : boolean = false ) => {
4859 setIsLoading ( true ) ;
49- const data = await getAllTickets ( ) ;
50- setTickets ( data as unknown as Ticket [ ] ) ;
51- setIsLoading ( false ) ;
60+ try {
61+ const result = await getAllTickets ( pageNum , 20 , filter ) ;
62+ if ( isReset ) {
63+ setTickets ( result . tickets as unknown as Ticket [ ] ) ;
64+ } else {
65+ setTickets ( prev => [ ...prev , ...( result . tickets as unknown as Ticket [ ] ) ] ) ;
66+ }
67+ setHasMore ( result . hasMore ) ;
68+ } catch ( error ) {
69+ console . error ( 'Error fetching tickets:' , error ) ;
70+ } finally {
71+ setIsLoading ( false ) ;
72+ }
73+ } ;
74+
75+ const loadMore = ( ) => {
76+ const nextPage = page + 1 ;
77+ setPage ( nextPage ) ;
78+ fetchTickets ( nextPage ) ;
5279 } ;
5380
5481 const handleReply = async ( ticketId : string ) => {
@@ -60,9 +87,10 @@ export default function AdminTicketsPage() {
6087
6188 if ( result . success ) {
6289 setReplyText ( prev => ( { ...prev , [ ticketId ] : '' } ) ) ;
63- setExpandedTicketId ( null ) ; // Close the conversation modal
64- setPostReplyActionId ( ticketId ) ; // Show post-reply options
65- fetchTickets ( ) ;
90+ setExpandedTicketId ( null ) ;
91+ setPostReplyActionId ( ticketId ) ;
92+ fetchTickets ( 1 , true ) ;
93+ fetchStats ( ) ;
6694 } else {
6795 setNotification ( { message : result . error || "Error sending guidance" , type : 'error' } ) ;
6896 }
@@ -74,7 +102,8 @@ export default function AdminTicketsPage() {
74102 const result = await closeTicket ( ticketId ) ;
75103 if ( result . success ) {
76104 setNotification ( { message : "Inquiry closed successfully" , type : 'success' } ) ;
77- fetchTickets ( ) ;
105+ fetchTickets ( 1 , true ) ;
106+ fetchStats ( ) ;
78107 } else {
79108 setNotification ( { message : "Error closing inquiry" , type : 'error' } ) ;
80109 }
@@ -105,7 +134,7 @@ export default function AdminTicketsPage() {
105134 : 'bg-white text-gray-400 border-gray-100 hover:bg-gray-50'
106135 } `}
107136 >
108- < span className = "flex-none" > { tickets . filter ( t => t . status === ' OPEN' ) . length } New</ span >
137+ < span className = "flex-none" > { stats . OPEN } New</ span >
109138 </ button >
110139 < button
111140 onClick = { ( ) => setFilter ( 'ANSWERED' ) }
@@ -114,7 +143,7 @@ export default function AdminTicketsPage() {
114143 : 'bg-white text-gray-400 border-gray-100 hover:bg-gray-50'
115144 } `}
116145 >
117- < span className = "flex-none" > { tickets . filter ( t => t . status === ' ANSWERED' ) . length } Answered</ span >
146+ < span className = "flex-none" > { stats . ANSWERED } Answered</ span >
118147 </ button >
119148 < button
120149 onClick = { ( ) => setFilter ( 'CLOSED' ) }
@@ -123,22 +152,21 @@ export default function AdminTicketsPage() {
123152 : 'bg-white text-gray-400 border-gray-100 hover:bg-gray-50'
124153 } `}
125154 >
126- < span className = "flex-none" > Closed</ span >
155+ < span className = "flex-none" > { stats . CLOSED } Closed</ span >
127156 </ button >
128157 </ div >
129158 </ div >
130159
131160 < div className = "grid gap-4 w-full mt-6 md:mt-10" >
132- { tickets . filter ( t => filter === 'ALL' || t . status === filter ) . length === 0 ? (
161+ { tickets . length === 0 ? (
133162 < div className = "text-center py-20 bg-white rounded-3xl border border-dashed border-gray-200 shadow-sm animate-in fade-in duration-500 mx-1" >
134163 < MessageCircle className = "w-12 h-12 text-gray-200 mx-auto mb-4" />
135164 < h3 className = "text-xl font-black text-gray-900 tracking-tight" > Nothing here</ h3 >
136165 < p className = "text-sm text-gray-400 font-medium" > No inquiries matching this status.</ p >
137166 </ div >
138167 ) : (
139- tickets
140- . filter ( t => filter === 'ALL' || t . status === filter )
141- . map ( ( ticket ) => (
168+ < >
169+ { tickets . map ( ( ticket ) => (
142170 < div
143171 key = { ticket . id }
144172 onClick = { ( ) => setExpandedTicketId ( ticket . id ) }
@@ -172,11 +200,34 @@ export default function AdminTicketsPage() {
172200 </ div >
173201 < div className = "flex flex-col items-end gap-1 flex-none" >
174202 < div className = "bg-gray-100/50 group-hover:bg-ochre group-hover:text-white px-2 md:px-3 py-1 md:py-1.5 rounded-lg md:rounded-xl text-[8px] md:text-[10px] font-black text-gray-500 transition-all border border-gray-100/10 uppercase tracking-widest shadow-sm" >
175- { ticket . messages . length } < span className = "hidden xs:inline" > MSG</ span >
203+ { ticket . messages . length } MSG
176204 </ div >
177205 </ div >
178206 </ div >
179- ) )
207+ ) ) }
208+
209+ { hasMore && (
210+ < div className = "flex justify-center pt-8 pb-12" >
211+ < button
212+ onClick = { ( e ) => {
213+ e . stopPropagation ( ) ;
214+ loadMore ( ) ;
215+ } }
216+ disabled = { isLoading }
217+ className = "bg-white text-ochre px-8 py-3 rounded-2xl font-black text-xs uppercase tracking-[0.2em] border border-ochre/20 hover:bg-orange-50 transition-all flex items-center shadow-sm disabled:opacity-50"
218+ >
219+ { isLoading ? (
220+ < >
221+ < Loader2 className = "w-4 h-4 mr-2 animate-spin" />
222+ Loading...
223+ </ >
224+ ) : (
225+ "Explore More"
226+ ) }
227+ </ button >
228+ </ div >
229+ ) }
230+ </ >
180231 ) }
181232 </ div >
182233
@@ -188,7 +239,6 @@ export default function AdminTicketsPage() {
188239 flush
189240 >
190241 < div className = "flex flex-col h-[70vh]" >
191- { /* Thread Content */ }
192242 < div className = "flex-1 overflow-y-auto p-4 space-y-3 no-scrollbar bg-gray-50/50" >
193243 { tickets . find ( t => t . id === expandedTicketId ) ?. messages . map ( ( msg , idx ) => (
194244 < div key = { idx } className = { `flex ${ msg . sender === 'USER' ? 'justify-start' : 'justify-end' } ` } >
@@ -206,7 +256,6 @@ export default function AdminTicketsPage() {
206256 ) ) }
207257 </ div >
208258
209- { /* Footer / Reply Area */ }
210259 { expandedTicketId && tickets . find ( t => t . id === expandedTicketId ) ?. status !== 'CLOSED' && (
211260 < div className = "p-4 bg-white border-t border-gray-100 pb-8 md:pb-4" >
212261 < textarea
@@ -278,7 +327,6 @@ export default function AdminTicketsPage() {
278327 </ div >
279328 </ Modal >
280329
281- { /* Premium UI Components */ }
282330 < Modal
283331 isOpen = { ! ! confirmCloseId }
284332 onClose = { ( ) => setConfirmCloseId ( null ) }
0 commit comments