From 492bf25cf903ebc2168c56c46fc51519189dc00c Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 12 Apr 2025 22:57:02 +0700 Subject: [PATCH 01/16] Add store --- package-lock.json | 112 ++++++++++++++++++++++++------ package.json | 2 + src/stores/hooks.ts | 6 ++ src/stores/index.ts | 14 ++++ src/stores/slices/accountSlice.ts | 42 +++++++++++ 5 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 src/stores/hooks.ts create mode 100644 src/stores/index.ts create mode 100644 src/stores/slices/accountSlice.ts diff --git a/package-lock.json b/package-lock.json index 6c7462d..baa055d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toolbar": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", + "@reduxjs/toolkit": "^2.6.1", "@tanstack/react-table": "^8.11.6", "@udecode/plate-alignment": "^24.5.2", "@udecode/plate-autoformat": "^24.5.2", @@ -88,6 +89,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", "react-lite-youtube-embed": "^2.3.52", + "react-redux": "^9.2.0", "react-resizable-panels": "^1.0.7", "react-router-dom": "^6.15.0", "react-sortablejs": "^6.1.4", @@ -2078,6 +2080,46 @@ "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.1.tgz", + "integrity": "sha512-SSlIqZNYhqm/oMkXbtofwZSt9lrncblzo6YcZ9zoX+zLngRBrCOjK4lNLdkNucJF58RHOWrD9txT3bT3piH7Zw==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz", @@ -2370,13 +2412,12 @@ "devOptional": true }, "node_modules/@types/react": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", - "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "version": "18.3.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", + "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", "devOptional": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -2389,12 +2430,6 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "devOptional": true - }, "node_modules/@types/semver": { "version": "7.5.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", @@ -2416,6 +2451,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.9.tgz", "integrity": "sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", @@ -4419,6 +4459,14 @@ "redux": "^4.2.0" } }, + "node_modules/dnd-core/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6242,6 +6290,28 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", @@ -6487,12 +6557,9 @@ } }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "node_modules/regenerator-runtime": { "version": "0.14.0", @@ -6511,6 +6578,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -7262,11 +7334,11 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/util-deprecate": { diff --git a/package.json b/package.json index 36f45e2..d225297 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toolbar": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", + "@reduxjs/toolkit": "^2.6.1", "@tanstack/react-table": "^8.11.6", "@udecode/plate-alignment": "^24.5.2", "@udecode/plate-autoformat": "^24.5.2", @@ -90,6 +91,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.47.0", "react-lite-youtube-embed": "^2.3.52", + "react-redux": "^9.2.0", "react-resizable-panels": "^1.0.7", "react-router-dom": "^6.15.0", "react-sortablejs": "^6.1.4", diff --git a/src/stores/hooks.ts b/src/stores/hooks.ts new file mode 100644 index 0000000..23652e9 --- /dev/null +++ b/src/stores/hooks.ts @@ -0,0 +1,6 @@ +import { useDispatch, useSelector } from "react-redux"; +import { AppDispatch, RootState } from "."; + +// Use throughout your app instead of plain `useDispatch` and `useSelector` +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..c92788c --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,14 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { accountSlice } from "./slices/accountSlice"; + +export const store = configureStore({ + reducer: { + account: accountSlice.reducer, + }, +}); + +// Infer the `RootState`, `AppDispatch`, and `AppStore` types from the store itself +export type RootState = ReturnType; +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch; +export type AppStore = typeof store; diff --git a/src/stores/slices/accountSlice.ts b/src/stores/slices/accountSlice.ts new file mode 100644 index 0000000..7b87d47 --- /dev/null +++ b/src/stores/slices/accountSlice.ts @@ -0,0 +1,42 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export interface AccountState { + username: string; + accessToken: string | null; + refreshToken: string | null; + expiresAt: string | null; +} + +const initialState: AccountState = { + username: "", + accessToken: null, + refreshToken: null, + expiresAt: null, +}; + +export const accountSlice = createSlice({ + name: "account", + initialState, + reducers: { + loadAccountFromLocal: (state: AccountState) => { + state.username = localStorage.getItem("username") || ""; + state.accessToken = localStorage.getItem("token") || null; + state.refreshToken = localStorage.getItem("refreshToken") || null; + state.expiresAt = localStorage.getItem("expiresAt") || null; + }, + setAccount: (state: AccountState, action) => { + state.username = action.payload.username; + }, + setAccessToken: (state: AccountState, action) => { + state.accessToken = action.payload.accessToken; + }, + setRefreshToken: (state: AccountState, action) => { + state.refreshToken = action.payload.refreshToken; + }, + setExpiresAt: (state: AccountState, action) => { + state.expiresAt = action.payload.expiresAt; + }, + }, +}); + +export const { loadAccountFromLocal } = accountSlice.actions; From 184dea656b04524a66f78ec5a1646a238aea64bf Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sat, 12 Apr 2025 23:15:35 +0700 Subject: [PATCH 02/16] Font family draft (Anuphun) --- index.html | 28 ++++--- src/components/ProblemViewLayout.tsx | 111 ++++++++++++++++++--------- src/index.css | 108 +++++++++++++------------- 3 files changed, 146 insertions(+), 101 deletions(-) diff --git a/index.html b/index.html index 0057fce..36372a6 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,19 @@ - + - - - - - Grader - - -
- - + + + + + + + + Grader + + +
+ + diff --git a/src/components/ProblemViewLayout.tsx b/src/components/ProblemViewLayout.tsx index c495479..b3d03ab 100644 --- a/src/components/ProblemViewLayout.tsx +++ b/src/components/ProblemViewLayout.tsx @@ -4,8 +4,14 @@ import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { ProgrammingLanguageOptions } from "../constants/ProgrammingLanguage"; -import { ProblemPoplulateCreatorModel, ProblemPopulateCreatorSecureModel } from "../types/models/Problem.model"; -import { GetSubmissionByAccountProblemResponse, SubmissionPopulateSubmissionTestcasesSecureModel } from "../types/models/Submission.model"; +import { + ProblemPoplulateCreatorModel, + ProblemPopulateCreatorSecureModel, +} from "../types/models/Problem.model"; +import { + GetSubmissionByAccountProblemResponse, + SubmissionPopulateSubmissionTestcasesSecureModel, +} from "../types/models/Submission.model"; import { handleDeprecatedDescription } from "../utilities/HandleDeprecatedDescription"; import { readableDateFormat } from "../utilities/ReadableDateFormat"; import PreviousSubmissionsCombobox from "./PreviousSubmissionsCombobox"; @@ -13,43 +19,57 @@ import ReadOnlyPlate from "./ReadOnlyPlate"; import TestcasesGradingIndicator from "./TestcasesGradingIndicator"; import { Button } from "./shadcn/Button"; import { Combobox } from "./shadcn/Combobox"; -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "./shadcn/Resizable"; +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "./shadcn/Resizable"; import { Separator } from "./shadcn/Seperator"; export type OnSubmitProblemViewLayoutCallback = { - setGrading: React.Dispatch> - setLastedSubmission: React.Dispatch> - selectedLanguage: string - submitCodeValue: string -} + setGrading: React.Dispatch>; + setLastedSubmission: React.Dispatch< + React.SetStateAction< + SubmissionPopulateSubmissionTestcasesSecureModel | undefined + > + >; + selectedLanguage: string; + submitCodeValue: string; +}; const ProblemViewLayout = ({ - onSubmit, - problem, - previousSubmissions -}:{ - onSubmit: (callback: OnSubmitProblemViewLayoutCallback) => void - problem: ProblemPoplulateCreatorModel | ProblemPopulateCreatorSecureModel - previousSubmissions: GetSubmissionByAccountProblemResponse + onSubmit, + problem, + previousSubmissions, +}: { + onSubmit: (callback: OnSubmitProblemViewLayoutCallback) => void; + problem: ProblemPoplulateCreatorModel | ProblemPopulateCreatorSecureModel; + previousSubmissions: GetSubmissionByAccountProblemResponse; }) => { + const navigate = useNavigate(); - const navigate = useNavigate(); - - // const [problem, setProblem] = useState(); + // const [problem, setProblem] = useState(); const [selectedLanguage, setSelectedLanguage] = useState("python"); const [grading, setGrading] = useState(false); - const [submitCodeValue, setSubmitCodeValue] = useState(""); + const [submitCodeValue, setSubmitCodeValue] = useState( + "" + ); - // const [previousSubmissions, setPreviousSubmissions] = - useState(); + // const [previousSubmissions, setPreviousSubmissions] = + useState(); const [lastedSubmission, setLastedSubmission] = useState(); - const handleSubmit = () => { - onSubmit({setGrading, setLastedSubmission,selectedLanguage,submitCodeValue}) - } + const handleSubmit = () => { + onSubmit({ + setGrading, + setLastedSubmission, + selectedLanguage, + submitCodeValue: submitCodeValue || "", + }); + }; - const handleSelectPreviousSubmission = (submissionId: string) => { + const handleSelectPreviousSubmission = (submissionId: string) => { let submission = null; if ( submissionId === previousSubmissions?.best_submission?.submission_id @@ -73,10 +93,13 @@ const ProblemViewLayout = ({ useEffect(() => { if (problem && problem?.allowed_languages.length > 0) { - setSelectedLanguage(ProgrammingLanguageOptions.filter(lang => problem?.allowed_languages.includes(lang.value))[0].value) + setSelectedLanguage( + ProgrammingLanguageOptions.filter((lang) => + problem?.allowed_languages.includes(lang.value) + )[0].value + ); } - },[problem]) - + }, [problem]); // useEffect(() => { @@ -88,8 +111,14 @@ const ProblemViewLayout = ({ // },[problem]) return ( - - + +
{problem?.title}
- +
Author @@ -123,9 +152,9 @@ const ProblemViewLayout = ({
*/}
-
- -
+
+ +
{problem && (
*/} - +
problem?.allowed_languages.includes(lang.value))} + options={ProgrammingLanguageOptions.filter((lang) => + problem?.allowed_languages.includes(lang.value) + )} onSelect={(value) => setSelectedLanguage(value)} // initialValue={selectedLanguage} value={selectedLanguage} @@ -196,7 +227,13 @@ const ProblemViewLayout = ({ } /> +
+ +
+ + {currentPage} / {total} + +
+ +
+ +
+
+ ); +}; + +export default CustomPagination; diff --git a/src/components/Tables/ProblemTables/MyProblemsTable.tsx b/src/components/Tables/ProblemTables/MyProblemsTable.tsx index c460852..c6c8cc2 100644 --- a/src/components/Tables/ProblemTables/MyProblemsTable.tsx +++ b/src/components/Tables/ProblemTables/MyProblemsTable.tsx @@ -16,7 +16,11 @@ import { readableDateFormat } from "../../../utilities/ReadableDateFormat"; import MyProblemDropdown from "../../Dropdowns/MyProblemDropdown"; import { DataTable } from "../Prototype/DataTable"; import DataTableSortableLayout from "../Prototype/DataTableSortableLayout"; -import { HoverCard, HoverCardContent, HoverCardTrigger } from "../../shadcn/HoverCard"; +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from "../../shadcn/HoverCard"; import { Badge } from "../../shadcn/Badge"; const columns: ColumnDef[] = [ diff --git a/src/components/Tables/Prototype/DataTable.tsx b/src/components/Tables/Prototype/DataTable.tsx index 940e57e..4fcd681 100644 --- a/src/components/Tables/Prototype/DataTable.tsx +++ b/src/components/Tables/Prototype/DataTable.tsx @@ -10,7 +10,7 @@ import { useReactTable, } from "@tanstack/react-table"; -import { DataTablePagination } from "./DataTablePagination"; +import { useState } from "react"; import { Table, TableBody, @@ -19,7 +19,6 @@ import { TableHeader, TableRow, } from "../../shadcn/Table"; -import { useState } from "react"; interface DataTableProps { columns: ColumnDef[]; @@ -100,7 +99,7 @@ export function DataTable({
- + {/* */}
{/*
- setSearchValue(e.target.value)} placeholder="Search ..." /> + setSearchValue(e.target.value)} + placeholder="Search ..." + />
- setTabValue(e)}> + setTabValue(e)} + > Personal @@ -85,13 +138,25 @@ const MyProblems = () => {
- - {tabValue === "personal" && } - {tabValue === "manageable" && } + {tabValue === "personal" && ( + + )} + {tabValue === "manageable" && ( + + )} + +
+
+ +
+
From 2dbf4b9b08cac8a4671bcf3c3a72013ef243d3d8 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 13 Apr 2025 11:35:54 +0700 Subject: [PATCH 04/16] Query to service instead --- .../Paginations/CustomPagination.tsx | 11 +++-- src/types/apis/Problem.api.ts | 19 +++++--- src/views/My/Problems/MyProblems.tsx | 43 +++++++------------ 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/components/Paginations/CustomPagination.tsx b/src/components/Paginations/CustomPagination.tsx index d239bb0..80593ba 100644 --- a/src/components/Paginations/CustomPagination.tsx +++ b/src/components/Paginations/CustomPagination.tsx @@ -8,18 +8,23 @@ const CustomPagination = ({ pagination, onNextClick, onPreviousClick, - disabled = false, + disabled = false, }: { pagination: Pagination; onNextClick: () => void; onPreviousClick: () => void; - disabled?: boolean; + disabled?: boolean; }) => { const { start, end, total } = pagination; const isFirstPage = useMemo(() => start === 0, [start]); const isLastPage = useMemo(() => end >= total, [end, total]); const currentPage = useMemo(() => Math.floor(start / 10) + 1, [start]); + const pageSize = useMemo(() => end - start, [end, start]); + const totalPages = useMemo( + () => Math.ceil(total / pageSize), + [total, pageSize] + ); return (
@@ -36,7 +41,7 @@ const CustomPagination = ({
- {currentPage} / {total} + {currentPage} / {totalPages}
diff --git a/src/types/apis/Problem.api.ts b/src/types/apis/Problem.api.ts index c8d83f4..dcdeddc 100644 --- a/src/types/apis/Problem.api.ts +++ b/src/types/apis/Problem.api.ts @@ -5,7 +5,7 @@ import { ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel, ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel, ProblemPopulateCreatorSecureModel, - ProblemPopulateTestcases + ProblemPopulateTestcases, } from "../models/Problem.model"; export type CreateProblemRequest = { @@ -60,7 +60,8 @@ export type GetAllProblemsQuery = { start?: number; end?: number; account_id?: string; -} + query?: string; +}; export type GetAllProblemsResponse = { problems: ProblemPopulateAccountAndSubmissionPopulateSubmissionTestcasesSecureModel[]; @@ -82,7 +83,9 @@ export type ProblemServiceAPI = { accountId: string, request: CreateProblemRequest ) => Promise>; - getAll: (query?: GetAllProblemsQuery) => Promise>; + getAll: ( + query?: GetAllProblemsQuery + ) => Promise>; getAllAsCreator: ( accountId: string, query?: GetAllProblemsQuery @@ -99,8 +102,10 @@ export type ProblemServiceAPI = { request: UpdateProblemRequest | CreateProblemRequest ) => Promise>; // deleteMultiple: (problemIds:string[]) => Promise>; - delete: (problemId: string, - accountId: string) => Promise>; + delete: ( + problemId: string, + accountId: string + ) => Promise>; updateGroupPermissions: ( problemId: string, accountId: string, @@ -111,5 +116,7 @@ export type ProblemServiceAPI = { validateProgram: ( request: ValidateProgramRequest ) => Promise>; - getPublic: (problemId: string) => Promise>; + getPublic: ( + problemId: string + ) => Promise>; }; diff --git a/src/views/My/Problems/MyProblems.tsx b/src/views/My/Problems/MyProblems.tsx index 9b3eccb..2f25870 100644 --- a/src/views/My/Problems/MyProblems.tsx +++ b/src/views/My/Problems/MyProblems.tsx @@ -11,21 +11,21 @@ import { NavSidebarContext } from "../../../contexts/NavSidebarContext"; import NavbarSidebarLayout from "../../../layout/NavbarSidebarLayout"; import { ProblemService } from "../../../services/Problem.service"; import { Pagination } from "../../../types/Pagination.type"; -import { ProblemCountTestcases } from "../../../types/models/Problem.model"; +import { ProblemPopulateTestcases } from "../../../types/models/Problem.model"; const MyProblems = () => { const accountId = String(localStorage.getItem("account_id")); const navigate = useNavigate(); - const [problems, setProblems] = useState([]); + const [problems, setProblems] = useState([]); const [manageableProblems, setManageableProblems] = useState< - ProblemCountTestcases[] + ProblemPopulateTestcases[] >([]); const [filteredProblems, setFilteredProblems] = useState< - ProblemCountTestcases[] + ProblemPopulateTestcases[] >([]); const [filteredManageableProblems, setFilteredManageableProblems] = - useState([]); + useState([]); const [problemPagination, setProblemPagination] = useState({ start: 0, end: 10, @@ -39,25 +39,8 @@ const MyProblems = () => { const [searchValue, setSearchValue] = useState(""); useEffect(() => { - if (!searchValue || searchValue === "") { - setFilteredProblems(problems); - setFilteredManageableProblems(manageableProblems); - } else { - setFilteredProblems( - problems.filter((problem) => - problem.title - .toLowerCase() - .includes(searchValue.toLowerCase()) - ) - ); - setFilteredManageableProblems( - manageableProblems.filter((problem) => - problem.title - .toLowerCase() - .includes(searchValue.toLowerCase()) - ) - ); - } + setFilteredProblems(problems); + setFilteredManageableProblems(manageableProblems); }, [searchValue, problems, manageableProblems]); useEffect(() => { @@ -65,6 +48,7 @@ const MyProblems = () => { ProblemService.getAllAsCreator(accountId, { start: problemPagination.start, end: problemPagination.end, + query: searchValue || "", }).then((response) => { setProblems(response.data.problems); setManageableProblems(response.data.manageable_problems); @@ -76,13 +60,18 @@ const MyProblems = () => { }); setSection("PROBLEMS"); - }, [accountId, problemPagination.start, problemPagination.end]); + }, [ + accountId, + problemPagination.start, + problemPagination.end, + searchValue, + ]); const handleNextClick = () => { if (problemPagination.end < problemPagination.total) { setProblemPagination((prev) => { const newStart = prev.start + 10; - const newEnd = Math.min(prev.end + 10, prev.total); + const newEnd = prev.end + 10; return { ...prev, start: newStart, end: newEnd }; }); } @@ -91,7 +80,7 @@ const MyProblems = () => { const handlePreviousClick = () => { if (problemPagination.start > 0) { setProblemPagination((prev) => { - const newStart = Math.max(prev.start - 10, 0); + const newStart = prev.start - 10; const newEnd = prev.end - 10; return { ...prev, start: newStart, end: newEnd }; }); From ba68ea2d8496b897ffa4b43c11a0cfa20bf178bb Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 13 Apr 2025 11:37:19 +0700 Subject: [PATCH 05/16] Add test build workflow --- .github/workflows/webpack.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/webpack.yml diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml new file mode 100644 index 0000000..d9e88b3 --- /dev/null +++ b/.github/workflows/webpack.yml @@ -0,0 +1,28 @@ +name: NodeJS with Webpack + +on: + push: + branches: [ "*" ] + pull_request: + branches: [ "*" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + npx webpack \ No newline at end of file From 6333b859228cb987c07a3a38262404edca65e2c4 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 13 Apr 2025 11:39:58 +0700 Subject: [PATCH 06/16] Remove workflow --- .github/workflows/webpack.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/webpack.yml diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml deleted file mode 100644 index d9e88b3..0000000 --- a/.github/workflows/webpack.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: NodeJS with Webpack - -on: - push: - branches: [ "*" ] - pull_request: - branches: [ "*" ] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [18.x, 20.x, 22.x] - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Build - run: | - npm install - npx webpack \ No newline at end of file From 4ff887f7dcc02819eaa52d1628cb2bf37d5aa862 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 13 Apr 2025 11:50:20 +0700 Subject: [PATCH 07/16] Always open link in new tab --- .../Dropdowns/MyProblemDropdown.tsx | 38 ++-- src/components/Tables/MyCollectionsTable.tsx | 151 +++++++------- src/components/Tables/MyCoursesTable.tsx | 32 ++- src/components/Tables/MyGroupsTable.tsx | 114 +++++------ .../Tables/MyPreviousSubmissionsTable.tsx | 12 +- .../Tables/MyProblemSubmissionsTable.tsx | 184 +++++++++--------- .../Tables/ProblemTables/MyProblemsTable.tsx | 5 +- .../ProblemTables/PublicProblemsTable.tsx | 14 +- src/views/My/Problems/ProblemStatistic.tsx | 7 +- 9 files changed, 286 insertions(+), 271 deletions(-) diff --git a/src/components/Dropdowns/MyProblemDropdown.tsx b/src/components/Dropdowns/MyProblemDropdown.tsx index a019e40..2161f4c 100644 --- a/src/components/Dropdowns/MyProblemDropdown.tsx +++ b/src/components/Dropdowns/MyProblemDropdown.tsx @@ -3,21 +3,21 @@ import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { ProblemService } from "../../services/Problem.service"; import { transformCreateProblemRequestForm2CreateProblemRequest } from "../../types/adapters/CreateProblemRequestForm.adapter"; +import { transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm } from "../../types/adapters/Problem.adapter"; import { - ProblemModel, - ProblemPopulateTestcases, - ProblemSecureModel, + ProblemModel, + ProblemPopulateTestcases, + ProblemSecureModel, } from "../../types/models/Problem.model"; import DeleteProblemConfirmationDialog from "../Dialogs/DeleteProblemConfirmationDialog"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, } from "../shadcn/DropdownMenu"; import { toast } from "../shadcn/UseToast"; -import { transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm } from "../../types/adapters/Problem.adapter"; const MyProblemDropdown = ({ children, @@ -36,11 +36,12 @@ const MyProblemDropdown = ({ problem.problem_id ); - let createRequest = transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm( - response.data - ) + const createRequest = + transformProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel2CreateProblemRequestForm( + response.data + ); - createRequest.title += " (Copy)" + createRequest.title += " (Copy)"; const { request, groups } = transformCreateProblemRequestForm2CreateProblemRequest( @@ -60,7 +61,10 @@ const MyProblemDropdown = ({ toast({ title: `Cloned ${response.data.title}`, }); - window.open(`/my/problems/${response.data.problem_id}`, "_blank"); + window.open( + `/my/problems/${response.data.problem_id}`, + "_blank" + ); }); }; @@ -88,9 +92,11 @@ const MyProblemDropdown = ({ - window.open(`/my/problems/${problem.problem_id}/edit`, "_blank") + window.open( + `/my/problems/${problem.problem_id}/edit`, + "_blank" + ) } - > Edit Problem diff --git a/src/components/Tables/MyCollectionsTable.tsx b/src/components/Tables/MyCollectionsTable.tsx index 0060f38..811016a 100644 --- a/src/components/Tables/MyCollectionsTable.tsx +++ b/src/components/Tables/MyCollectionsTable.tsx @@ -1,82 +1,79 @@ -import { ColumnDef } from '@tanstack/react-table' -import { FileSpreadsheet, Folder, MoreHorizontal } from 'lucide-react' -import { Link } from 'react-router-dom' -import { CollectionPopulateCollectionProblemPopulateProblemModel } from '../../types/models/Collection.model' -import { readableDateFormat } from '../../utilities/ReadableDateFormat' -import { DataTable } from './Prototype/DataTable' -import MyCollectionDropdown from '../Dropdowns/MyCollectionDropdown' +import { ColumnDef } from "@tanstack/react-table"; +import { FileSpreadsheet, Folder, MoreHorizontal } from "lucide-react"; +import { Link } from "react-router-dom"; +import { CollectionPopulateCollectionProblemPopulateProblemModel } from "../../types/models/Collection.model"; +import { readableDateFormat } from "../../utilities/ReadableDateFormat"; +import { DataTable } from "./Prototype/DataTable"; +import MyCollectionDropdown from "../Dropdowns/MyCollectionDropdown"; -const columns: ColumnDef[] = [ - { - accessorKey: "name", - header: "Title", - cell: ({ row }) => ( -
- - - {row.original.name} - -
- ) - }, - { - accessorKey: "problems", - header: "Problems", - cell: ({ row }) => ( -
- - {row.original.problems.length} -
- ) - }, - { - accessorKey: "updated_date", - header: "Updated Date", - cell: ({ row }) => ( -
- {readableDateFormat(row.original.updated_date)} -
- ), - }, - { - accessorKey: "action", - header: () => ( -
- Action -
- ), - cell: ({ row }) => ( -
- - - -
- ), - }, - // { - // accessorKey: "created_date", - // header: "Created Date", - // cell: ({ row }) => ( - //
- // {readableDateFormat(row.original.created_date)} - //
- // ), - // }, -] +const columns: ColumnDef[] = + [ + { + accessorKey: "name", + header: "Title", + cell: ({ row }) => ( +
+ + + {row.original.name} + +
+ ), + }, + { + accessorKey: "problems", + header: "Problems", + cell: ({ row }) => ( +
+ + {row.original.problems.length} +
+ ), + }, + { + accessorKey: "updated_date", + header: "Updated Date", + cell: ({ row }) => ( +
+ {readableDateFormat(row.original.updated_date)} +
+ ), + }, + { + accessorKey: "action", + header: () =>
Action
, + cell: ({ row }) => ( +
+ + + +
+ ), + }, + // { + // accessorKey: "created_date", + // header: "Created Date", + // cell: ({ row }) => ( + //
+ // {readableDateFormat(row.original.created_date)} + //
+ // ), + // }, + ]; const MyCollectionsTable = ({ - collections=[] -}:{ - collections?: CollectionPopulateCollectionProblemPopulateProblemModel[] + collections = [], +}: { + collections?: CollectionPopulateCollectionProblemPopulateProblemModel[]; }) => { - return ( -
- -
- ) -} + return ( +
+ +
+ ); +}; -export default MyCollectionsTable \ No newline at end of file +export default MyCollectionsTable; diff --git a/src/components/Tables/MyCoursesTable.tsx b/src/components/Tables/MyCoursesTable.tsx index ad38364..e57304f 100644 --- a/src/components/Tables/MyCoursesTable.tsx +++ b/src/components/Tables/MyCoursesTable.tsx @@ -22,7 +22,10 @@ const MyCoursesTable = ({ className="mr-2 text-purple-400" size={20} /> - + {row.original.name}
@@ -33,30 +36,23 @@ const MyCoursesTable = ({ header: "Collections", cell: ({ row }) => (
- + {row.original.collections.length}
), }, - { - accessorKey: "updated_date", - header: "Updated Date", - cell: ({ row }) => ( -
- {readableDateFormat(row.original.updated_date)} -
- ), - }, { - accessorKey: "action", - header: () => ( -
- Action + accessorKey: "updated_date", + header: "Updated Date", + cell: ({ row }) => ( +
+ {readableDateFormat(row.original.updated_date)}
), + }, + { + accessorKey: "action", + header: () =>
Action
, cell: ({ row }) => (
diff --git a/src/components/Tables/MyGroupsTable.tsx b/src/components/Tables/MyGroupsTable.tsx index 7326898..9e6b02c 100644 --- a/src/components/Tables/MyGroupsTable.tsx +++ b/src/components/Tables/MyGroupsTable.tsx @@ -1,60 +1,64 @@ -import { ColumnDef } from '@tanstack/react-table' -import { User, Users } from 'lucide-react' -import { Link } from 'react-router-dom' -import { GroupPopulateGroupMemberPopulateAccountSecureModel } from '../../types/models/Group.model' -import { readableDateFormat } from '../../utilities/ReadableDateFormat' -import { DataTable } from './Prototype/DataTable' +import { ColumnDef } from "@tanstack/react-table"; +import { User, Users } from "lucide-react"; +import { Link } from "react-router-dom"; +import { GroupPopulateGroupMemberPopulateAccountSecureModel } from "../../types/models/Group.model"; +import { readableDateFormat } from "../../utilities/ReadableDateFormat"; +import { DataTable } from "./Prototype/DataTable"; const MyGroupsTable = ({ - groups=[] -}:{ - groups: GroupPopulateGroupMemberPopulateAccountSecureModel[] + groups = [], +}: { + groups: GroupPopulateGroupMemberPopulateAccountSecureModel[]; }) => { + const column: ColumnDef[] = + [ + { + accessorKey: "name", + header: "Title", + cell: ({ row }) => ( +
+ + + {row.original.name} + +
+ ), + }, + { + accessorKey: "members", + header: "Members", + cell: ({ row }) => ( +
+ + {row.original.members.length} +
+ ), + }, + { + accessorKey: "updated_date", + header: "Updated Date", + cell: ({ row }) => ( +
+ {readableDateFormat(row.original.updated_date)} +
+ ), + }, + ]; - const column:ColumnDef[] = [ - { - accessorKey: "name", - header: "Title", - cell: ({ row }) => ( -
- - - {row.original.name} - -
- ) - }, - { - accessorKey: "members", - header: "Members", - cell: ({ row }) => ( -
- - {row.original.members.length} -
- ) - }, - { - accessorKey: "updated_date", - header: "Updated Date", - cell: ({ row }) => ( -
- {readableDateFormat(row.original.updated_date)} -
- ), - }, - ] + return ( +
+ +
+ ); +}; - return ( -
- -
- ) -} - -export default MyGroupsTable \ No newline at end of file +export default MyGroupsTable; diff --git a/src/components/Tables/MyPreviousSubmissionsTable.tsx b/src/components/Tables/MyPreviousSubmissionsTable.tsx index 231f999..1e46e9e 100644 --- a/src/components/Tables/MyPreviousSubmissionsTable.tsx +++ b/src/components/Tables/MyPreviousSubmissionsTable.tsx @@ -20,6 +20,7 @@ const MyPreviousSubmissionsTable = ({ cell: ({ row }) => (
{row.original.topic ? (
- + {row.original.topic.name} @@ -69,10 +74,7 @@ const MyPreviousSubmissionsTable = ({ }, { accessorKey: "is_passed", - header: () =>
- - Is Passed -
, + header: () =>
Is Passed
, cell: ({ row }) => { return (
diff --git a/src/components/Tables/MyProblemSubmissionsTable.tsx b/src/components/Tables/MyProblemSubmissionsTable.tsx index 4a51e7d..1ec12b8 100644 --- a/src/components/Tables/MyProblemSubmissionsTable.tsx +++ b/src/components/Tables/MyProblemSubmissionsTable.tsx @@ -10,36 +10,37 @@ import TestcasesGradingIndicator from "../TestcasesGradingIndicator"; import { Button } from "../shadcn/Button"; import { Link } from "react-router-dom"; - - const MyProblemSubmissionsTable = ({ submissions = [], - problem + problem, }: { submissions?: SubmissionPopulateSubmissionTestcaseAndAccountModel[]; - problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel + problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel; }) => { - - const columns: ColumnDef[] = - [ - { - accessorKey: "username", - header: "Username", - cell: ({ row }) => ( -
- {row.original.account.username} -
- ), - }, - { - accessorKey: "course", - header: "Course", - cell: ({ row }) => ( -
+ const columns: ColumnDef[] = + [ + { + accessorKey: "username", + header: "Username", + cell: ({ row }) => ( +
+ {row.original.account.username} +
+ ), + }, + { + accessorKey: "course", + header: "Course", + cell: ({ row }) => ( +
{row.original.topic ? (
- + {row.original.topic.name} @@ -51,78 +52,77 @@ const MyProblemSubmissionsTable = ({
)}
- ), - }, - { - accessorKey: "language", - header: () =>
Language
, - cell: ({ row }) => ( -
- { - ProgrammingLanguageOptions.find( - (language) => - language.value === row.original.language - )?.badge - } -
- ), - }, - { - accessorKey: "is_passed", - header: () =>
- - Is Passed -
, - cell: ({ row }) => { - return ( -
- {row.original.is_passed ? ( - - ) : ( - - )} + ), + }, + { + accessorKey: "language", + header: () =>
Language
, + cell: ({ row }) => ( +
+ { + ProgrammingLanguageOptions.find( + (language) => + language.value === row.original.language + )?.badge + } +
+ ), + }, + { + accessorKey: "is_passed", + header: () =>
Is Passed
, + cell: ({ row }) => { + return ( +
+ {row.original.is_passed ? ( + + ) : ( + + )} +
+ ); + }, + }, + { + accessorKey: "runtime_result", + header: "Runtime Result", + cell: ({ row }) => ( +
+ +
+ ), + }, + { + accessorKey: "date", + header: "Submitted Date", + cell: ({ row }) => ( +
+ {readableDateFormat(row.original.date)} +
+ ), + }, + { + accessorKey: "action", + header: "", + cell: ({ row }) => ( +
+ + +
- ); + ), }, - }, - { - accessorKey: "runtime_result", - header: "Runtime Result", - cell: ({ row }) => ( -
- -
- ), - }, - { - accessorKey: "date", - header: "Submitted Date", - cell: ({ row }) => ( -
{readableDateFormat(row.original.date)}
- ), - }, - { - accessorKey: "action", - header: "", - cell: ({ row }) => ( -
- - - -
- ), - }, - ]; + ]; return (
diff --git a/src/components/Tables/ProblemTables/MyProblemsTable.tsx b/src/components/Tables/ProblemTables/MyProblemsTable.tsx index c6c8cc2..9fd4736 100644 --- a/src/components/Tables/ProblemTables/MyProblemsTable.tsx +++ b/src/components/Tables/ProblemTables/MyProblemsTable.tsx @@ -39,7 +39,10 @@ const columns: ColumnDef[] = [ return (
- + {row.original.title}
diff --git a/src/components/Tables/ProblemTables/PublicProblemsTable.tsx b/src/components/Tables/ProblemTables/PublicProblemsTable.tsx index c05eb35..6fbe821 100644 --- a/src/components/Tables/ProblemTables/PublicProblemsTable.tsx +++ b/src/components/Tables/ProblemTables/PublicProblemsTable.tsx @@ -7,9 +7,9 @@ import TestcasesGradingIndicator from "../../TestcasesGradingIndicator"; import { Badge } from "../../shadcn/Badge"; import { Button } from "../../shadcn/Button"; import { - HoverCard, - HoverCardContent, - HoverCardTrigger, + HoverCard, + HoverCardContent, + HoverCardTrigger, } from "../../shadcn/HoverCard"; import { DataTable } from "../Prototype/DataTable"; @@ -34,7 +34,10 @@ const PublicProblemsTable = ({ className="mr-2 text-blue-400" size={20} /> - + {row.original.title}
@@ -132,7 +135,7 @@ const PublicProblemsTable = ({ ), }, - { + { accessorKey: "author", header: "Author", cell: ({ row }) => ( @@ -166,6 +169,7 @@ const PublicProblemsTable = ({ cell: ({ row }) => (
{ ProblemService.get(accountId, problemId) .then((response) => { setProblem(response.data); - document.title = `${response.data.title}` + document.title = `${response.data.title}`; return SubmissionService.getByCreatorProblem( accountId, problemId @@ -43,7 +43,10 @@ const ProblemStatistic = () => {
- +
+ +
+ +
); From 8b82f6d971ae4dbd23c5a4f73f8c905c3d32fcf0 Mon Sep 17 00:00:00 2001 From: KanonKC Date: Sun, 13 Apr 2025 15:51:25 +0700 Subject: [PATCH 10/16] Download input/output file has problem title in filename --- ...issionSourceCodeAndRuntimeResultDialog.tsx | 5 +- .../TestcaseValidationAccordion.tsx | 65 +++++++++++++------ src/utilities/String.ts | 7 ++ 3 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 src/utilities/String.ts diff --git a/src/components/Dialogs/ProblemSubmissionSourceCodeAndRuntimeResultDialog.tsx b/src/components/Dialogs/ProblemSubmissionSourceCodeAndRuntimeResultDialog.tsx index fd4d7d8..3359a3b 100644 --- a/src/components/Dialogs/ProblemSubmissionSourceCodeAndRuntimeResultDialog.tsx +++ b/src/components/Dialogs/ProblemSubmissionSourceCodeAndRuntimeResultDialog.tsx @@ -10,11 +10,11 @@ import { Separator } from "../shadcn/Seperator"; const ProblemSubmissionSourceCodeAndRuntimeResultDialog = ({ submission, children, - problem + problem }: { submission: SubmissionPopulateSubmissionTestcaseAndAccountModel; children?: React.ReactNode; - problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel + problem: ProblemPopulateAccountAndTestcasesAndProblemGroupPermissionsPopulateGroupModel }) => { const [sourceCode, setSourceCode] = React.useState( submission.submission_code @@ -44,6 +44,7 @@ const ProblemSubmissionSourceCodeAndRuntimeResultDialog = ({
({ ...output, input: problem.testcases[index]?.input diff --git a/src/components/TestcaseValidationAccordion.tsx b/src/components/TestcaseValidationAccordion.tsx index 79f0a37..103e4b7 100644 --- a/src/components/TestcaseValidationAccordion.tsx +++ b/src/components/TestcaseValidationAccordion.tsx @@ -1,6 +1,6 @@ import { FileDown } from "lucide-react"; import { RuntimeResult } from "../types/apis/Problem.api"; -import { TestcaseModel } from "../types/models/Problem.model"; +import { ProblemModel, TestcaseModel } from "../types/models/Problem.model"; import { Accordion, AccordionContent, @@ -10,6 +10,7 @@ import { import { Badge } from "./shadcn/Badge"; import { Label } from "./shadcn/Label"; import { Textarea } from "./shadcn/Textarea"; +import { convertToSnakeCase } from "../utilities/String"; const minimizer = (text: string | null): string => { const LIMIT = 250; @@ -22,36 +23,36 @@ const minimizer = (text: string | null): string => { return text; }; -const downloadTextfile = (filename: string, text: string | null) => { - if (!text) return; - - const element = document.createElement("a"); - const file = new Blob([text], { type: "text/plain" }); - element.href = URL.createObjectURL(file); - element.download = filename; - document.body.appendChild(element); // Required for this to work in FireFox - element.click(); -}; - -const DownloadMiniButton = ({...args}:{ +const DownloadMiniButton = ({ + ...args +}: { className?: string; onClick?: React.MouseEventHandler; }) => { return ( -

Download .txt

- ) -} +

+ Download .txt +

+ ); +}; const TestcaseValidationInstance = ({ + problem, value, inputValue, outputValue, status, + index, }: { + problem: ProblemModel; value: string; inputValue: string; outputValue: string | null; status: string; + index: number; }) => { // const [inputValue, setInputValue] = useState("1 2 3"); // const [outputValue, setOutputValue] = useState("Hello World!"); @@ -60,6 +61,19 @@ const TestcaseValidationInstance = ({ // console.log(inputValue, outputValue, status); // }, [outputValue]); + const downloadTextfile = (type: string, text: string | null) => { + if (!text) return; + + const element = document.createElement("a"); + const file = new Blob([text], { type: "text/plain" }); + element.href = URL.createObjectURL(file); + element.download = `${problem.problem_id}_${convertToSnakeCase( + problem.title + )}_${type}_${index + 1}.txt`; + document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + }; + return ( @@ -79,7 +93,11 @@ const TestcaseValidationInstance = ({
- downloadTextfile("input.txt", inputValue)}/> + + downloadTextfile("input", inputValue) + } + />