11import { useEffect , useState } from "react" ;
2+ import { AnimatePresence , motion } from "framer-motion" ;
23import { ApiService } from "@/lib/api" ;
34import type { Team } from "@/lib/types" ;
45
@@ -8,6 +9,12 @@ export default function TeamsTable() {
89 const [ page , setPage ] = useState ( 0 ) ;
910 const [ loading , setLoading ] = useState ( true ) ;
1011 const [ error , setError ] = useState < string | null > ( null ) ;
12+ const [ selectedTeam , setSelectedTeam ] = useState < Team | null > ( null ) ;
13+ const [ panelOpen , setPanelOpen ] = useState ( false ) ;
14+ const [ panelError , setPanelError ] = useState < string | null > ( null ) ;
15+ const [ saving , setSaving ] = useState ( false ) ;
16+ const [ hasChanges , setHasChanges ] = useState ( false ) ;
17+ const [ pendingPaymentStatus , setPendingPaymentStatus ] = useState < boolean | null > ( null ) ;
1118
1219 const rowsPerPage = 10 ; // Number of rows per page
1320
@@ -66,6 +73,209 @@ export default function TeamsTable() {
6673 text-sm sm:text-base text-gray-800 dark:text-gray-100"
6774 />
6875 </ div >
76+ { /* Slide-in Side Panel */ }
77+ < AnimatePresence >
78+ { panelOpen && (
79+ < motion . div
80+ className = "fixed inset-0 z-50 flex items-center justify-end"
81+ initial = { { opacity : 0 } }
82+ animate = { { opacity : 1 } }
83+ exit = { { opacity : 0 } }
84+ >
85+ { /* Background overlay */ }
86+ < div
87+ className = "absolute inset-0 bg-black/30"
88+ onClick = { ( ) => {
89+ setPanelOpen ( false ) ;
90+ setSelectedTeam ( null ) ;
91+ setPanelError ( null ) ;
92+ setHasChanges ( false ) ;
93+ setPendingPaymentStatus ( null ) ;
94+ } }
95+ />
96+
97+ { /* Panel */ }
98+ < motion . div
99+ className = "relative bg-white dark:bg-gray-900 shadow-xl w-full sm:w-[500px] max-h-screen overflow-auto p-6 border-l border-gray-200 dark:border-gray-700"
100+ initial = { { x : '100%' } }
101+ animate = { { x : 0 } }
102+ exit = { { x : '100%' } }
103+ transition = { { type : 'spring' , stiffness : 300 , damping : 30 } }
104+ >
105+ < div className = "flex items-center justify-between mb-4" >
106+ < h2 className = "text-lg font-bold text-gray-800 dark:text-white" > Team Details</ h2 >
107+ < button
108+ onClick = { ( ) => {
109+ setPanelOpen ( false ) ;
110+ setSelectedTeam ( null ) ;
111+ setPanelError ( null ) ;
112+ setHasChanges ( false ) ;
113+ setPendingPaymentStatus ( null ) ;
114+ } }
115+ className = "text-gray-600 dark:text-gray-300 hover:text-gray-900"
116+ >
117+ Close
118+ </ button >
119+ </ div >
120+
121+ { panelError ? (
122+ < div className = "text-sm text-red-500" > { panelError } </ div >
123+ ) : selectedTeam ? (
124+ < div className = "space-y-4" >
125+ { /* Team Basic Info */ }
126+ < div className = "bg-orange-50 dark:bg-orange-900/20 p-4 rounded-lg" >
127+ < h3 className = "font-semibold text-orange-700 dark:text-orange-300 mb-3" > Team Information</ h3 >
128+ < div className = "grid grid-cols-2 gap-3 text-sm" >
129+ < div >
130+ < span className = "font-medium text-gray-600 dark:text-gray-400" > Team ID:</ span >
131+ < div className = "text-gray-800 dark:text-gray-200" > { selectedTeam . teamId } </ div >
132+ </ div >
133+ < div >
134+ < span className = "font-medium text-gray-600 dark:text-gray-400" > SCC ID:</ span >
135+ < div className = "text-gray-800 dark:text-gray-200" > { selectedTeam . scc_id } </ div >
136+ </ div >
137+ < div className = "col-span-2" >
138+ < span className = "font-medium text-gray-600 dark:text-gray-400" > Team Name:</ span >
139+ < div className = "text-gray-800 dark:text-gray-200 font-medium" > { selectedTeam . title } </ div >
140+ </ div >
141+ </ div >
142+ </ div >
143+
144+ { /* Team Members */ }
145+ < div className = "bg-gray-50 dark:bg-gray-800/50 p-4 rounded-lg" >
146+ < h3 className = "font-semibold text-gray-700 dark:text-gray-300 mb-3" >
147+ Team Members ({ selectedTeam . members . length } )
148+ </ h3 >
149+ < div className = "space-y-4" >
150+ { selectedTeam . members . map ( ( member , index ) => (
151+ < div key = { member . id } className = "bg-white dark:bg-gray-700 p-3 rounded-lg border border-gray-200 dark:border-gray-600" >
152+ < div className = "flex items-start justify-between mb-2" >
153+ < h4 className = "font-medium text-gray-800 dark:text-gray-200" >
154+ { member . name }
155+ { index === 0 && < span className = "ml-2 text-xs bg-orange-100 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 px-2 py-1 rounded-full" > Leader</ span > }
156+ </ h4 >
157+ < span className = "text-xs bg-gray-100 dark:bg-gray-600 text-gray-600 dark:text-gray-300 px-2 py-1 rounded" >
158+ Year { member . year_of_study }
159+ </ span >
160+ </ div >
161+
162+ < div className = "grid grid-cols-1 sm:grid-cols-2 gap-2 text-xs text-gray-600 dark:text-gray-400" >
163+ < div >
164+ < span className = "font-medium" > Email:</ span >
165+ < div className = "text-gray-800 dark:text-gray-300 break-all" > { member . email } </ div >
166+ </ div >
167+ < div >
168+ < span className = "font-medium" > Phone:</ span >
169+ < div className = "text-gray-800 dark:text-gray-300" > { member . phone_number } </ div >
170+ </ div >
171+ < div >
172+ < span className = "font-medium" > Department:</ span >
173+ < div className = "text-gray-800 dark:text-gray-300" > { member . department } </ div >
174+ </ div >
175+ < div >
176+ < span className = "font-medium" > T-Shirt:</ span >
177+ < div className = "text-gray-800 dark:text-gray-300" > { member . tShirtSize } </ div >
178+ </ div >
179+ < div className = "sm:col-span-2" >
180+ < span className = "font-medium" > College:</ span >
181+ < div className = "text-gray-800 dark:text-gray-300" > { member . college_name } </ div >
182+ </ div >
183+ { member . location && (
184+ < div >
185+ < span className = "font-medium" > Location:</ span >
186+ < div className = "text-gray-800 dark:text-gray-300" > { member . location } </ div >
187+ </ div >
188+ ) }
189+ < div >
190+ < span className = "font-medium" > Attendance:</ span >
191+ < div className = "text-gray-800 dark:text-gray-300" > { member . attendance } </ div >
192+ </ div >
193+ </ div >
194+ </ div >
195+ ) ) }
196+ </ div >
197+ </ div >
198+
199+ { /* Payment Verification Section */ }
200+ < div className = "bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg" >
201+ < h3 className = "font-semibold text-blue-700 dark:text-blue-300 mb-3" > Payment Status</ h3 >
202+ < div className = "mb-4" >
203+ < label className = "flex items-center gap-3" >
204+ < input
205+ type = "checkbox"
206+ checked = { pendingPaymentStatus ?? false }
207+ onChange = { ( e ) => {
208+ const newValue = e . target . checked ;
209+ setPendingPaymentStatus ( newValue ) ;
210+ setHasChanges ( newValue !== ( selectedTeam ?. paymentVerified ?? false ) ) ;
211+ } }
212+ className = "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
213+ />
214+ < span className = "text-gray-700 dark:text-gray-300" > Payment Verified</ span >
215+ < span className = { `px-2 py-1 text-xs rounded-full ${
216+ pendingPaymentStatus
217+ ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300'
218+ : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
219+ } `} >
220+ { pendingPaymentStatus ? 'Verified' : 'Pending' }
221+ </ span >
222+ </ label >
223+ </ div >
224+
225+ { /* Save Button */ }
226+ { hasChanges && (
227+ < div className = "flex gap-2" >
228+ < button
229+ onClick = { async ( ) => {
230+ if ( ! selectedTeam || pendingPaymentStatus === null ) return ;
231+
232+ try {
233+ setSaving ( true ) ;
234+ setPanelError ( null ) ;
235+ await ApiService . admin . verifyTeamPayment ( selectedTeam . teamId , pendingPaymentStatus ) ;
236+
237+ // Update local UI
238+ const updatedTeam = { ...selectedTeam , paymentVerified : pendingPaymentStatus } ;
239+ setSelectedTeam ( updatedTeam ) ;
240+ setTeams ( ( prev ) => prev . map ( ( t ) =>
241+ t . teamId === selectedTeam . teamId
242+ ? { ...t , paymentVerified : pendingPaymentStatus }
243+ : t
244+ ) ) ;
245+ setHasChanges ( false ) ;
246+ } catch ( err ) {
247+ console . error ( 'Failed to update payment status' , err ) ;
248+ setPanelError ( 'Failed to update payment status' ) ;
249+ } finally {
250+ setSaving ( false ) ;
251+ }
252+ } }
253+ disabled = { saving }
254+ className = "px-4 py-2 bg-green-500 hover:bg-green-600 disabled:bg-gray-400 text-white text-sm rounded-lg transition"
255+ >
256+ { saving ? 'Saving...' : 'Save Changes' }
257+ </ button >
258+ < button
259+ onClick = { ( ) => {
260+ setPendingPaymentStatus ( selectedTeam ?. paymentVerified ?? false ) ;
261+ setHasChanges ( false ) ;
262+ } }
263+ disabled = { saving }
264+ className = "px-4 py-2 bg-gray-500 hover:bg-gray-600 disabled:bg-gray-400 text-white text-sm rounded-lg transition"
265+ >
266+ Cancel
267+ </ button >
268+ </ div >
269+ ) }
270+ </ div >
271+ </ div >
272+ ) : (
273+ < div className = "text-sm text-gray-500" > No team selected</ div >
274+ ) }
275+ </ motion . div >
276+ </ motion . div >
277+ ) }
278+ </ AnimatePresence >
69279
70280 { /* Main Content */ }
71281 < div className = "max-w-5xl mx-auto" >
@@ -92,7 +302,14 @@ export default function TeamsTable() {
92302 paginatedTeams . map ( ( team ) => (
93303 < tr
94304 key = { team . teamId }
95- className = "border-b border-gray-200 dark:border-gray-700 hover:bg-orange-50 dark:hover:bg-orange-600/20 transition"
305+ className = "border-b border-gray-200 dark:border-gray-700 hover:bg-orange-50 dark:hover:bg-orange-600/20 transition cursor-pointer"
306+ onClick = { ( ) => {
307+ setSelectedTeam ( team ) ;
308+ setPanelOpen ( true ) ;
309+ setPanelError ( null ) ;
310+ setHasChanges ( false ) ;
311+ setPendingPaymentStatus ( team . paymentVerified ?? false ) ;
312+ } }
96313 >
97314 < td className = "px-4 py-3 text-gray-800 dark:text-white" > { team . teamId } </ td >
98315 < td className = "px-4 py-3 text-gray-800 dark:text-gray-100" > { team . scc_id } </ td >
0 commit comments