1+ "use client" ;
2+ import React , { useState , useEffect } from 'react' ;
3+ import { useQuery } from '@tanstack/react-query' ;
4+ import { DataTable } from "@/components/table/data-table" ;
5+ import { DataTableColumnHeader } from "@/components/table/data-table-column-header" ;
6+ import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card" ;
7+ import { Select , SelectContent , SelectItem , SelectTrigger , SelectValue } from "@/components/ui/select" ;
8+ import { fetchTeams } from "@/api/fetchTeams" ;
9+ import { fetchSubmission , type Submission } from "@/api/fetchIdeas" ;
10+ import { type Team } from "@/data/schema" ;
11+ import { type ColumnDef } from "@tanstack/react-table" ;
12+
13+ interface TeamData {
14+ id : string ;
15+ name : string | null ;
16+ numberOfPeople : number ;
17+ roundQualified : number ;
18+ submission : Submission | null ;
19+ }
20+
21+ export default function TeamsIdeasTable ( ) {
22+ const [ pageLimit , setPageLimit ] = useState ( 10 ) ;
23+ const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
24+ const [ currentPage , setCurrentPage ] = useState ( 0 ) ;
25+ const [ selectedTrack , setSelectedTrack ] = useState < string > ( "" ) ;
26+ const [ filteredData , setFilteredData ] = useState < TeamData [ ] > ( [ ] ) ;
27+ const [ availableTracks , setAvailableTracks ] = useState < string [ ] > ( [ ] ) ;
28+
29+ const { data : teamsData , isLoading : teamsLoading , isError : teamsError } = useQuery ( {
30+ queryKey : [ "teams" , currentPage , pageLimit ] ,
31+ queryFn : ( ) => fetchTeams ( {
32+ limit : pageLimit ,
33+ cursorId : undefined
34+ } ) ,
35+ } ) ;
36+
37+ const { data : processedData , isLoading : submissionsLoading } = useQuery ( {
38+ queryKey : [ "teams-submissions" , teamsData ?. teams ] ,
39+ queryFn : async ( ) => {
40+ if ( ! teamsData ?. teams ) return [ ] ;
41+
42+ const teamsWithSubmissions = await Promise . all (
43+ teamsData . teams . map ( async ( team : Team ) => {
44+ if ( ! team . ID ) return null ;
45+
46+ const submission = await fetchSubmission ( team . ID ) ;
47+
48+ return {
49+ id : team . ID ,
50+ name : team . Name ,
51+ numberOfPeople : team . NumberOfPeople ,
52+ roundQualified : team . RoundQualified ,
53+ submission : submission
54+ } as TeamData ;
55+ } )
56+ ) ;
57+
58+ return teamsWithSubmissions . filter ( ( team ) : team is TeamData => ! ! team ) ;
59+ } ,
60+ enabled : ! ! teamsData ?. teams ,
61+ } ) ;
62+
63+ useEffect ( ( ) => {
64+ if ( processedData ) {
65+ const tracks = new Set < string > ( ) ;
66+ processedData . forEach ( team => {
67+ if ( team . submission ?. track ) {
68+ tracks . add ( team . submission . track ) ;
69+ }
70+ } ) ;
71+ setAvailableTracks ( Array . from ( tracks ) ) ;
72+ }
73+ } , [ processedData ] ) ;
74+
75+ useEffect ( ( ) => {
76+ if ( ! processedData ) return ;
77+
78+ const filtered = processedData . filter ( team => {
79+ const matchesSearch = searchTerm
80+ ? ( team . name ?. toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) || false ) ||
81+ ( team . submission ?. title ?. toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) || false )
82+ : true ;
83+
84+ const matchesTrack = selectedTrack
85+ ? team . submission ?. track === selectedTrack
86+ : true ;
87+
88+ return matchesSearch && matchesTrack ;
89+ } ) ;
90+
91+ setFilteredData ( filtered ) ;
92+ } , [ processedData , searchTerm , selectedTrack ] ) ;
93+
94+ const columns : ColumnDef < TeamData , unknown > [ ] = [
95+ {
96+ accessorKey : "name" ,
97+ header : ( { column } ) => (
98+ < DataTableColumnHeader column = { column } title = "Team Name" />
99+ ) ,
100+ cell : ( { row } ) => (
101+ < div className = "max-w-[200px] truncate font-medium" >
102+ { row . getValue ( "name" ) || "Unnamed Team" }
103+ </ div >
104+ ) ,
105+ } ,
106+ {
107+ accessorKey : "numberOfPeople" ,
108+ header : ( { column } ) => (
109+ < DataTableColumnHeader column = { column } title = "Team Size" />
110+ ) ,
111+ cell : ( { row } ) => (
112+ < div className = "text-center" >
113+ { row . getValue ( "numberOfPeople" ) }
114+ </ div >
115+ ) ,
116+ } ,
117+ {
118+ id : "submissionTitle" ,
119+ accessorFn : ( row ) => row . submission ?. title ,
120+ header : ( { column } ) => (
121+ < DataTableColumnHeader column = { column } title = "Submission Title" />
122+ ) ,
123+ cell : ( { row } ) => (
124+ < div className = "max-w-[200px] truncate" >
125+ { row . original . submission ?. title || "No submission" }
126+ </ div >
127+ ) ,
128+ } ,
129+ {
130+ id : "submissionDescription" ,
131+ accessorFn : ( row ) => row . submission ?. description ,
132+ header : ( { column } ) => (
133+ < DataTableColumnHeader column = { column } title = "Description" />
134+ ) ,
135+ cell : ( { row } ) => (
136+ < div className = "max-w-[300px] truncate" >
137+ { row . original . submission ?. description || "No description" }
138+ </ div >
139+ ) ,
140+ } ,
141+ {
142+ id : "track" ,
143+ accessorFn : ( row ) => row . submission ?. track ,
144+ header : ( { column } ) => (
145+ < DataTableColumnHeader column = { column } title = "Track" />
146+ ) ,
147+ cell : ( { row } ) => (
148+ < div className = "max-w-[200px] truncate" >
149+ { row . original . submission ?. track || "Unassigned" }
150+ </ div >
151+ ) ,
152+ } ,
153+ ] ;
154+
155+ if ( teamsLoading || submissionsLoading ) {
156+ return (
157+ < div className = "p-8 flex justify-center" >
158+ < div className = "text-lg" > Loading teams and submissions...</ div >
159+ </ div >
160+ ) ;
161+ }
162+
163+ if ( teamsError ) {
164+ return (
165+ < div className = "p-8 flex justify-center" >
166+ < div className = "text-lg text-red-500" > Error loading teams data</ div >
167+ </ div >
168+ ) ;
169+ }
170+
171+ return (
172+ < Card className = "w-full" >
173+ < CardHeader >
174+ < CardTitle > Teams & Submissions Dashboard </ CardTitle >
175+ </ CardHeader >
176+ < CardContent >
177+ < div className = "mb-6 flex flex-wrap gap-4" >
178+ < input
179+ type = "text"
180+ placeholder = "Search teams or submissions..."
181+ className = "w-64 rounded-md border border-gray-300 p-2"
182+ value = { searchTerm }
183+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
184+ />
185+ < Select
186+ value = { selectedTrack || "all" }
187+ onValueChange = { ( value ) => setSelectedTrack ( value === "all" ? "" : value ) }
188+ >
189+ < SelectTrigger className = "w-48" >
190+ < SelectValue placeholder = "Filter by track" />
191+ </ SelectTrigger >
192+ < SelectContent >
193+ < SelectItem value = "all" > All Tracks</ SelectItem >
194+ { availableTracks . map ( ( track ) => (
195+ < SelectItem key = { track } value = { track } >
196+ { track }
197+ </ SelectItem >
198+ ) ) }
199+ </ SelectContent >
200+ </ Select >
201+ </ div >
202+ < DataTable
203+ columns = { columns }
204+ data = { filteredData }
205+ handleNextPage = { ( ) => setCurrentPage ( prev => prev + 1 ) }
206+ handlePrevPage = { ( ) => setCurrentPage ( prev => prev - 1 ) }
207+ setPageLimit = { setPageLimit }
208+ pageLimit = { pageLimit }
209+ />
210+ </ CardContent >
211+ </ Card >
212+ ) ;
213+ }
0 commit comments