diff --git a/package.json b/package.json index fec9eb7..d290125 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-table": "^8.21.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.562.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15b1d29..ab28ea5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@tailwindcss/vite': specifier: ^4.1.18 version: 4.1.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -772,6 +775,17 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -2071,6 +2085,14 @@ snapshots: tailwindcss: 4.1.18 vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2) + '@tanstack/react-table@8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@tanstack/table-core@8.21.3': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.5 diff --git a/src/App.tsx b/src/App.tsx index 6f84961..e346e7f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,10 +5,10 @@ import { } from "react-router-dom"; import { Login } from "./pages/auth/Login"; import { Home } from "./pages/student/Home"; -import { Practice } from "./pages/student/Practice"; +import { Drills } from "./pages/student/Drills"; import { Rewards } from "./pages/student/Rewards"; import { Profile } from "./pages/student/Profile"; -import { Progress } from "./pages/student/Progress"; +import { Lessons } from "./pages/student/Lessons"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { StudentLayout } from "./pages/student/StudentLayout"; @@ -30,12 +30,12 @@ function App() { element: , }, { - path: "practice", - element: , + path: "drills", + element: , }, { - path: "progress", - element: , + path: "lessons", + element: , }, { path: "rewards", diff --git a/src/components/data-table/columns.tsx b/src/components/data-table/columns.tsx new file mode 100644 index 0000000..0c54c72 --- /dev/null +++ b/src/components/data-table/columns.tsx @@ -0,0 +1,22 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import type { TestDate } from "../../types/testdate"; + +export const columns: ColumnDef[] = [ + { + accessorKey: "date", + header: "Test Date", + }, + { + accessorKey: "deadline", + header: "Registration Deadline", + }, + { + accessorKey: "latedeadline", + header: "Late Registration Deadline", + }, + { + accessorKey: "is_digital", + header: "Action", + cell: ({ row }) => {row.original.is_digital ? "Yes" : "No"}, + }, +]; diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx new file mode 100644 index 0000000..801e895 --- /dev/null +++ b/src/components/data-table/data-table.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { + type ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableHeader, + TableRow, + TableBody, + TableCell, + TableHead, +} from "../ui/table"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + +

+ No SAT dates available right now. +

+
+
+ )} +
+
+
+ ); +} diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..5513a5c --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,114 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( +
+ + + ) +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return ( + + ) +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + + ) +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + tr]:last:border-b-0", + className + )} + {...props} + /> + ) +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + + ) +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( +
[role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + [role=checkbox]]:translate-y-[2px]", + className + )} + {...props} + /> + ) +} + +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { + return ( +
+ ) +} + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/pages/student/Practice.tsx b/src/pages/student/Drills.tsx similarity index 94% rename from src/pages/student/Practice.tsx rename to src/pages/student/Drills.tsx index e7898a0..a1cb2b0 100644 --- a/src/pages/student/Practice.tsx +++ b/src/pages/student/Drills.tsx @@ -1,13 +1,13 @@ import { useAuthStore } from "../../stores/authStore"; -export const Practice = () => { +export const Drills = () => { const user = useAuthStore((state) => state.user); return (
-

Practice

+

Drills

Email: {user?.email}

Role: {user?.role}

diff --git a/src/pages/student/Home.tsx b/src/pages/student/Home.tsx index a53656d..84eae68 100644 --- a/src/pages/student/Home.tsx +++ b/src/pages/student/Home.tsx @@ -6,11 +6,10 @@ import { TabsContent, } from "../../components/ui/tabs"; import { useAuthStore } from "../../stores/authStore"; -import { Search } from "lucide-react"; +import { CheckCircle, Search } from "lucide-react"; import { api } from "../../utils/api"; import { Card, - CardAction, CardContent, CardDescription, CardFooter, @@ -19,13 +18,19 @@ import { } from "../../components/ui/card"; import { Badge } from "../../components/ui/badge"; import { Button } from "../../components/ui/button"; +import type { PracticeSheet } from "../../types/sheet"; export const Home = () => { const user = useAuthStore((state) => state.user); - const [practiceSheets, setPracticeSheets] = useState([]); + // const logout = useAuthStore((state) => state.logout); // const navigate = useNavigate(); + const [practiceSheets, setPracticeSheets] = useState([]); + const [notStartedSheets, setNotStartedSheets] = useState([]); + const [inProgressSheets, setInProgressSheets] = useState([]); + const [completedSheets, setCompletedSheets] = useState([]); + useEffect(() => { const fetchPracticeSheets = async () => { if (!user) return; @@ -45,11 +50,28 @@ export const Home = () => { } const sheets = await api.getPracticeSheets(token, 1, 10); setPracticeSheets(sheets.data); + sortPracticeSheets(sheets.data); } catch (error) { console.error("Error fetching practice sheets:", error); } }; + const sortPracticeSheets = (sheets: PracticeSheet[]) => { + const notStarted = sheets.filter( + (sheet) => sheet.user_status === "NOT_STARTED" + ); + const inProgress = sheets.filter( + (sheet) => sheet.user_status === "in-progress" + ); + const completed = sheets.filter( + (sheet) => sheet.user_status === "completed" + ); + + setNotStartedSheets(notStarted); + setInProgressSheets(inProgress); + setCompletedSheets(completed); + }; + fetchPracticeSheets(); }, [user]); @@ -59,7 +81,7 @@ export const Home = () => {

Welcome, {user?.name || "Student"}

-
+
{
-
-
- - + +
+ + - All status + All Not Started @@ -102,7 +124,66 @@ export const Home = () => {
- {practiceSheets.map((sheet) => ( + {practiceSheets.length < 0 ? ( + practiceSheets.slice(0, 4).map((sheet) => ( + + + + {sheet?.title} + + + {sheet?.description} + + + +

+ Not Started +

+ + {sheet?.modules_count} modules + +
+ +

+ {sheet?.time_limit} minutes +

+
+ + + +
+ )) + ) : ( +
+

+ No Practice Sheets available. +

+
+ )} + {practiceSheets.length > 4 && ( +
+ +
+ )} +
+
+ +
+ {notStartedSheets.map((sheet) => ( @@ -138,17 +219,116 @@ export const Home = () => { ))}
- -

Not Started

-
-

In Progress

+ {inProgressSheets.map((sheet) => ( + + + + {sheet?.title} + + + {sheet?.subject} + + + +

Not Started

+ + {sheet?.modules_count} modules + +
+ +

+ {sheet?.time_limit} minutes +

+
+ + + +
+ ))}
-

Completed

+ {completedSheets.map((sheet) => ( + + + + {sheet?.title} + + + {sheet?.subject} + + + +

Not Started

+ + {sheet?.modules_count} modules + +
+ +

+ {sheet?.time_limit} minutes +

+
+ + + +
+ ))}
-
+ +
+
+

+ SAT Preparation Tips +

+
+
+ +

+ Practice regularly with official SAT materials +

+
+
+ +

+ Review your mistakes and learn from them +

+
+
+ +

Focus on your weak areas

+
+
+ +

+ Take full-length practice tests +

+
+
+ +

+ Get plenty of rest before the test day +

+
+
+
); diff --git a/src/pages/student/Progress.tsx b/src/pages/student/Lessons.tsx similarity index 94% rename from src/pages/student/Progress.tsx rename to src/pages/student/Lessons.tsx index 85a9cea..4cccbf9 100644 --- a/src/pages/student/Progress.tsx +++ b/src/pages/student/Lessons.tsx @@ -1,13 +1,13 @@ import { useAuthStore } from "../../stores/authStore"; -export const Progress = () => { +export const Lessons = () => { const user = useAuthStore((state) => state.user); return (
-

Progress

+

Lessons

Email: {user?.email}

Role: {user?.role}

diff --git a/src/pages/student/StudentLayout.tsx b/src/pages/student/StudentLayout.tsx index bf82d12..f847fed 100644 --- a/src/pages/student/StudentLayout.tsx +++ b/src/pages/student/StudentLayout.tsx @@ -1,5 +1,13 @@ import { Outlet, NavLink, useNavigate } from "react-router-dom"; -import { Home, BookOpen, TrendingUp, Award, User, Menu } from "lucide-react"; +import { + Home, + BookOpen, + TrendingUp, + Award, + User, + Menu, + Video, +} from "lucide-react"; import { useAuthStore } from "../../stores/authStore"; export function StudentLayout() { @@ -14,8 +22,8 @@ export function StudentLayout() { const navItems = [ { to: "/student/home", icon: Home, label: "Home" }, - { to: "/student/practice", icon: BookOpen, label: "Practice" }, - { to: "/student/progress", icon: TrendingUp, label: "Progress" }, + { to: "/student/drills", icon: BookOpen, label: "Drills" }, + { to: "/student/lessons", icon: Video, label: "Lessons" }, { to: "/student/rewards", icon: Award, label: "Rewards" }, { to: "/student/profile", icon: User, label: "Profile" }, ]; diff --git a/src/types/sheet.ts b/src/types/sheet.ts new file mode 100644 index 0000000..317ab56 --- /dev/null +++ b/src/types/sheet.ts @@ -0,0 +1,23 @@ +interface CreatedBy { + id: string; + name: string; + email: string; +} + +export interface PracticeSheet { + title: string; + difficulty: string; + time_limit: number; + description: string; + is_locked: boolean; + id: string; + created_by_id: string; + created_at: string; + updated_at: string; + questions_count: number; + topics: string[]; + created_by: CreatedBy; + modules: string[]; + user_status: string; + modules_count: number; +} diff --git a/src/types/testdate.ts b/src/types/testdate.ts new file mode 100644 index 0000000..262e3b9 --- /dev/null +++ b/src/types/testdate.ts @@ -0,0 +1,11 @@ +import type { ReactNode } from "react"; + +export interface TestDate { + action: ReactNode; + test_date: string; + registration_deadline: string; + late_registration_deadline: string; + location: string; + is_digital: boolean; + id: string; +} diff --git a/src/utils/api.ts b/src/utils/api.ts index d93b9b8..8afb9b5 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,4 +1,4 @@ -const API_URL = "https://dsat-api.edbridgescholars.com"; +const API_URL = "https://ed-dev-api.omukk.dev"; export interface LoginRequest { email: string; @@ -27,25 +27,6 @@ export interface ApiError { message?: string; } -export interface PracticeSheet { - title: string; - subject: string; - difficulty: string; - time_limit: number; - description: string; - topics: string[]; - is_locked: boolean; - id: string; - created_at: string; - created_by: { - id: string; - name: string; - email: string; - }; - modules: string[]; - modules_count: number; -} - class ApiClient { private baseURL: string; @@ -128,6 +109,10 @@ class ApiClient { token ); } + + async getSatDates(token: string): Promise { + return this.authenticatedRequest(`/sat-dates/`, token); + } } export const api = new ApiClient(API_URL);