Skip to content

Commit 7c06b9d

Browse files
committed
Ideas table
1 parent 0916c3c commit 7c06b9d

3 files changed

Lines changed: 269 additions & 1 deletion

File tree

src/api/fetchIdeas.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import axios from "./axiosConfig";
2+
import { z } from "zod";
3+
4+
const submissionSchema = z.object({
5+
title: z.string().nullable(),
6+
description: z.string().nullable(),
7+
track: z.string().nullable(),
8+
github_link: z.string().nullable(),
9+
figma_link: z.string().nullable(),
10+
other_link: z.string().nullable(),
11+
team_id: z.string()
12+
});
13+
14+
const submissionResponseSchema = z.object({
15+
status: z.string(),
16+
data: submissionSchema.nullable()
17+
});
18+
19+
export type Submission = z.infer<typeof submissionSchema>;
20+
export type SubmissionResponse = z.infer<typeof submissionResponseSchema>;
21+
22+
export const fetchSubmission = async (teamId: string): Promise<Submission | null> => {
23+
try {
24+
const response = await axios.get(`/submission/get`, {
25+
params: { teamId },
26+
headers: {
27+
'Content-Type': 'application/json',
28+
},
29+
withCredentials: true
30+
});
31+
32+
const parsedResponse = submissionResponseSchema.parse(response.data);
33+
34+
if (parsedResponse.status === "success" && parsedResponse.data) {
35+
return parsedResponse.data;
36+
}
37+
38+
return null;
39+
} catch (error) {
40+
console.error(`Error fetching submission for team ${teamId}:`, error);
41+
return null;
42+
}
43+
};

src/app/idea/page.tsx

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
}

src/data/schema.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ export const TeamsResponseSchema = z.object({
6363
}),
6464
});
6565

66+
const ideaSchema = z.object({
67+
id: z.string(),
68+
Title: z.string(),
69+
Description: z.string(),
70+
Track: z.string(),
71+
team_members:z.array(z.object({
72+
FirstName: z.string().optional(),
73+
LastName: z.string().optional()
74+
})).optional()
75+
})
76+
6677

6778
export const TeamResponseSchema = z.object({
6879
status: z.string(),
@@ -151,4 +162,5 @@ export type UserResponse = z.infer<typeof usersResponseSchema>;
151162
export type TeamResponse = z.infer<typeof TeamResponseSchema>; //for searching by id
152163
export type TeamsResponse = z.infer<typeof TeamsResponseSchema>; //for fetching all the teams
153164
export type TeamFromSearch = z.infer<typeof TeamFromSearchSchema>;
154-
export type TeamsFromSearch = z.infer<typeof TeamsFromSearchSchema>;
165+
export type TeamsFromSearch = z.infer<typeof TeamsFromSearchSchema>;
166+
export type Idea = z.infer<typeof ideaSchema>

0 commit comments

Comments
 (0)