diff --git a/package-lock.json b/package-lock.json index 7b2c5ba..fbd2535 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "next-themes": "^0.4.3", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.53.2", + "react-hook-form": "^7.54.0", "recharts": "^2.14.1", "sonner": "^1.7.0", "tailwind-merge": "^2.5.5", @@ -5017,9 +5017,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.53.2", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz", - "integrity": "sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==", + "version": "7.54.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.0.tgz", + "integrity": "sha512-PS05+UQy/IdSbJNojBypxAo9wllhHgGmyr8/dyGQcPoiMf3e7Dfb9PWYVRco55bLbxH9S+1yDDJeTdlYCSxO3A==", "engines": { "node": ">=18.0.0" }, diff --git a/package.json b/package.json index 2028bd7..7f5e1e6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "next-themes": "^0.4.3", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.53.2", + "react-hook-form": "^7.54.0", "recharts": "^2.14.1", "sonner": "^1.7.0", "tailwind-merge": "^2.5.5", diff --git a/src/components/results/columns.tsx b/src/components/results/columns.tsx deleted file mode 100644 index af7f592..0000000 --- a/src/components/results/columns.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { ColumnDef } from "@tanstack/react-table" -import { Trash2 } from "lucide-react" -import { Input } from "@/components/ui/input" -import { Button } from "@/components/ui/button" -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" - -// This type is used to define the shape of our data. -// You can use a Zod schema here if you want. -export type Result = { - year: number - unitCode: string - unitTitle?: string - teachingPeriod?: string - creditPoints: number - mark: number - grade: string -} - -export const columns: ColumnDef[] = [ - { - accessorKey: "unitCode", - header: () =>
Unit Code
, - cell: ({ row }) => { - return ( - - ) - }, - }, - { - accessorKey: "creditPoints", - header: () =>
Credit Points
, - cell: ({ row }) => { - return - }, - }, - { - accessorKey: "mark", - header: () =>
Mark
, - cell: ({ row }) => { - return - }, - }, - { - accessorKey: "grade", - header: () =>
Grade
, - cell: ({ row }) => { - return ( - - ) - }, - }, - { - accessorKey: "delete", - header: "", - cell: () => { - return - }, - }, -] diff --git a/src/components/results/data-table.tsx b/src/components/results/data-table.tsx deleted file mode 100644 index 2f9cad6..0000000 --- a/src/components/results/data-table.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { - ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, -} from "@tanstack/react-table" - -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/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 results. - - - )} - -
-
- ) -} diff --git a/src/components/results/result-form-row.tsx b/src/components/results/result-form-row.tsx new file mode 100644 index 0000000..6299db9 --- /dev/null +++ b/src/components/results/result-form-row.tsx @@ -0,0 +1,132 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { resultSchema, type Result } from "@/schemas/result-schema" +import { Trash2 } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +interface ResultFormRowProps { + defaultValues?: Result + onDelete?: () => void + onChange?: (values: Result) => void +} + +export function ResultFormRow({ defaultValues, onDelete, onChange }: ResultFormRowProps) { + const form = useForm({ + resolver: zodResolver(resultSchema), + defaultValues: defaultValues || { + unitCode: "", + creditPoints: 0, + mark: 0, + grade: "N", + }, + }) + + const handleChange = form.handleSubmit((values) => { + onChange?.(values) + }) + + return ( +
+ +
+ ( + + + + + + + )} + /> + ( + + + field.onChange(Number(e.target.value))} + className="text-sm md:text-base" + /> + + + + )} + /> + ( + + + field.onChange(Number(e.target.value))} + className="text-sm md:text-base" + /> + + + + )} + /> + ( + + + + )} + /> + +
+
+ + ) +} \ No newline at end of file diff --git a/src/components/results/result-table.tsx b/src/components/results/result-table.tsx new file mode 100644 index 0000000..2edbfdc --- /dev/null +++ b/src/components/results/result-table.tsx @@ -0,0 +1,36 @@ +import { ResultFormRow } from "@/components/results/result-form-row" +import { Result } from "@/schemas/result-schema" + +interface ResultTableProps { + data: Result[] + onResultUpdate: (index: number, values: Result) => void + onResultDelete: (index: number) => void +} + +export function ResultTable({ data, onResultUpdate, onResultDelete }: ResultTableProps) { + return ( +
+
+
+
+
Unit Code
+
Credit Points
+
Mark
+
Grade
+
+
+
+
+ {data.map((result) => ( + onResultUpdate(result.id, values)} + onDelete={() => onResultDelete(result.id)} + /> + ))} +
+
+
+ ) +} \ No newline at end of file diff --git a/src/lib/calculate.ts b/src/lib/calculate.ts index 26d1935..15ed192 100644 --- a/src/lib/calculate.ts +++ b/src/lib/calculate.ts @@ -1,4 +1,4 @@ -import { Result } from "@/components/results/columns" +import { Result } from "@/schemas/result-schema" function calculateWAM(results: Result[]): number { const excludedGrades = [ diff --git a/src/routes/index.lazy.tsx b/src/routes/index.lazy.tsx index 282a849..679f015 100644 --- a/src/routes/index.lazy.tsx +++ b/src/routes/index.lazy.tsx @@ -1,8 +1,9 @@ import { createLazyFileRoute } from '@tanstack/react-router' +import { useState } from 'react' import { useTheme } from "@/components/theme/theme-provider" import { calculateWAM, calculateGPA, calculateColor } from "@/lib/calculate" -import { Result, columns } from "@/components/results/columns" -import { DataTable } from "@/components/results/data-table" +import { Result } from "@/schemas/result-schema" +import { ResultTable } from "@/components/results/result-table" import { RadialChart } from "@/components/results/radial-chart" import { Card, @@ -15,122 +16,120 @@ export const Route = createLazyFileRoute('/')({ component: Index, }) -function getData(): Result[] { - return [ - { - year: 2021, - unitCode: "ENG1002", - creditPoints: 6, - mark: 75, - grade: "D", - }, - { - year: 2021, - unitCode: "ENG1003", - creditPoints: 6, - mark: 80, - grade: "HD", - }, - { - year: 2021, - unitCode: "ENG1090", - creditPoints: 6, - mark: 75, - grade: "D", - }, - { - year: 2021, - unitCode: "PHS1001", - creditPoints: 6, - mark: 71, - grade: "D", - }, - { - year: 2021, - unitCode: "ECE2072", - creditPoints: 6, - mark: 50, - grade: "P", - }, - { - year: 2021, - unitCode: "ENG1001", - creditPoints: 6, - mark: 70, - grade: "D", - }, - { - year: 2021, - unitCode: "ENG1005", - creditPoints: 6, - mark: 83, - grade: "HD", - }, - { - year: 2021, - unitCode: "ENG1060", - creditPoints: 6, - mark: 89, - grade: "HD", - }, - { - year: 2022, - unitCode: "FIT1045", - creditPoints: 6, - mark: 95, - grade: "HD", - }, - { - year: 2022, - unitCode: "FIT2085", - creditPoints: 6, - mark: 79, - grade: "D", - }, - { - year: 2022, - unitCode: "FIT2099", - creditPoints: 6, - mark: 77, - grade: "D", - }, - { - year: 2022, - unitCode: "MAT1830", - creditPoints: 6, - mark: 83, - grade: "HD", - }, - { - year: 2022, - unitCode: "FIT2004", - creditPoints: 6, - mark: 52, - grade: "P", - }, - { - year: 2022, - unitCode: "FIT2100", - creditPoints: 6, - mark: 79, - grade: "D", - }, - { - year: 2022, - unitCode: "FIT2101", - creditPoints: 6, - mark: 82, - grade: "HD", - }, - { - year: 2022, - unitCode: "FIT2107", - creditPoints: 6, - mark: 64, - grade: "C", - }, - ] -} +const initialData: Result[] = [ + { + id: 0, + unitCode: "ENG1002", + creditPoints: 6, + mark: 75, + grade: "D", + }, + { + id: 1, + unitCode: "ENG1003", + creditPoints: 6, + mark: 80, + grade: "HD", + }, + { + id: 2, + unitCode: "ENG1090", + creditPoints: 6, + mark: 75, + grade: "D", + }, + { + id: 3, + unitCode: "PHS1001", + creditPoints: 6, + mark: 71, + grade: "D", + }, + { + id: 4, + unitCode: "ECE2072", + creditPoints: 6, + mark: 50, + grade: "P", + }, + { + id: 5, + unitCode: "ENG1001", + creditPoints: 6, + mark: 70, + grade: "D", + }, + { + id: 6, + unitCode: "ENG1005", + creditPoints: 6, + mark: 83, + grade: "HD", + }, + { + id: 7, + unitCode: "ENG1060", + creditPoints: 6, + mark: 89, + grade: "HD", + }, + { + id: 8, + unitCode: "FIT1045", + creditPoints: 6, + mark: 95, + grade: "HD", + }, + { + id: 9, + unitCode: "FIT2085", + creditPoints: 6, + mark: 79, + grade: "D", + }, + { + id: 10, + unitCode: "FIT2099", + creditPoints: 6, + mark: 77, + grade: "D", + }, + { + id: 11, + unitCode: "MAT1830", + creditPoints: 6, + mark: 83, + grade: "HD", + }, + { + id: 12, + unitCode: "FIT2004", + creditPoints: 6, + mark: 52, + grade: "P", + }, + { + id: 13, + unitCode: "FIT2100", + creditPoints: 6, + mark: 79, + grade: "D", + }, + { + id: 14, + unitCode: "FIT2101", + creditPoints: 6, + mark: 82, + grade: "HD", + }, + { + id: 15, + unitCode: "FIT2107", + creditPoints: 6, + mark: 64, + grade: "C", + }, +] type StatCardProps = { title: string @@ -170,7 +169,18 @@ function StatCard({ title, subtitle, value, maxValue }: StatCardProps) { } function Index() { - const data = getData() + const [data, setData] = useState(initialData) + + const handleResultUpdate = (id: number, updatedResult: Result) => { + setData(prevData => prevData.map(item => + item.id === id ? { ...updatedResult, id } : item + )) + } + + const handleResultDelete = (id: number) => { + setData(prevData => prevData.filter(item => item.id !== id)) + } + const wam = calculateWAM(data) const maxWam = 100 const gpa = calculateGPA(data) @@ -205,7 +215,11 @@ function Index() { ))}
- +
) diff --git a/src/schemas/result-schema.ts b/src/schemas/result-schema.ts new file mode 100644 index 0000000..aaea366 --- /dev/null +++ b/src/schemas/result-schema.ts @@ -0,0 +1,11 @@ +import { z } from "zod" + +export const resultSchema = z.object({ + id: z.number(), + unitCode: z.string().length(7), + creditPoints: z.number().min(0).max(24), + mark: z.number().min(0).max(100), + grade: z.enum(["HD", "D", "C", "P", "N", "NH", "NSR", "SFR", "WN"]), +}) + +export type Result = z.infer