From 9e7bebb2a3dae33082e97eb4cada8db96336310b Mon Sep 17 00:00:00 2001 From: Woojin Jung <44637040+jwoojin9@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:14:29 +0900 Subject: [PATCH] feat(fe): refresh problems dropdown list after submit (#2212) * feat(fe): refresh problems dropdown list after submit * fix(fe): make dropdown component * chore(fe): fix code * feat(fe): add submission result store * fix(fe): use tanstack query * fix(fe): use tanstack query * fix(fe): move code * fix(fe): resolve infinite rendering problem * fix(fe): resolve review * chore(fe): apply code review --------- Co-authored-by: Kohminchae <72334086+Kohminchae@users.noreply.github.com> Co-authored-by: Kohminchae --- .../_components/ContestProblemDropdown.tsx | 69 +++++++++++++++++++ .../_components/EditorHeader/EditorHeader.tsx | 6 ++ .../_components/EditorLayout.tsx | 63 +++-------------- apps/frontend/stores/editor.ts | 1 + 4 files changed, 85 insertions(+), 54 deletions(-) create mode 100644 apps/frontend/app/(client)/(code-editor)/_components/ContestProblemDropdown.tsx diff --git a/apps/frontend/app/(client)/(code-editor)/_components/ContestProblemDropdown.tsx b/apps/frontend/app/(client)/(code-editor)/_components/ContestProblemDropdown.tsx new file mode 100644 index 000000000..335dab63c --- /dev/null +++ b/apps/frontend/app/(client)/(code-editor)/_components/ContestProblemDropdown.tsx @@ -0,0 +1,69 @@ +'use client' + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from '@/components/shadcn/dropdown-menu' +import { cn, convertToLetter, fetcherWithAuth } from '@/libs/utils' +import checkIcon from '@/public/icons/check-green.svg' +import type { ContestProblem, ProblemDetail } from '@/types/type' +import { useQuery } from '@tanstack/react-query' +import Image from 'next/image' +import Link from 'next/link' +import { FaSortDown } from 'react-icons/fa' + +interface ContestProblemsResponse { + data: ContestProblem[] + total: number +} + +interface ContestProblemDropdownProps { + problem: ProblemDetail + problemId: number + contestId: number +} + +export default function ContestProblemDropdown({ + problem, + problemId, + contestId +}: ContestProblemDropdownProps) { + const { data: contestProblems } = useQuery< + ContestProblemsResponse | undefined + >({ + queryKey: ['contest', contestId, 'problems'], + queryFn: () => + fetcherWithAuth.get(`contest/${contestId}/problem?take=20`).json() + }) + + return ( + + +

{`${convertToLetter(contestProblems?.data.find((item) => item.id === Number(problemId))?.order as number)}. ${problem.title}`}

+ +
+ + {contestProblems?.data.map((p) => ( + + + {`${convertToLetter(p.order)}. ${p.title}`} + {p.submissionTime && ( +
+ check +
+ )} +
+ + ))} +
+
+ ) +} diff --git a/apps/frontend/app/(client)/(code-editor)/_components/EditorHeader/EditorHeader.tsx b/apps/frontend/app/(client)/(code-editor)/_components/EditorHeader/EditorHeader.tsx index c16cafbf0..7b727fcd4 100644 --- a/apps/frontend/app/(client)/(code-editor)/_components/EditorHeader/EditorHeader.tsx +++ b/apps/frontend/app/(client)/(code-editor)/_components/EditorHeader/EditorHeader.tsx @@ -36,6 +36,7 @@ import type { Submission, Template } from '@/types/type' +import { useQueryClient } from '@tanstack/react-query' import JSConfetti from 'js-confetti' import { Save } from 'lucide-react' import type { Route } from 'next' @@ -83,6 +84,8 @@ export default function Editor({ const whereToPush = useRef('') const isModalConfrimed = useRef(false) + const queryClient = useQueryClient() + useInterval( async () => { const res = await fetcherWithAuth(`submission/${submissionId}`, { @@ -186,6 +189,9 @@ export default function Editor({ storeCodeToLocalStorage(code) const submission: Submission = await res.json() setSubmissionId(submission.id) + queryClient.refetchQueries({ + queryKey: ['contest', contestId, 'problems'] + }) } else { setIsSubmitting(false) if (res.status === 401) { diff --git a/apps/frontend/app/(client)/(code-editor)/_components/EditorLayout.tsx b/apps/frontend/app/(client)/(code-editor)/_components/EditorLayout.tsx index 6c81a4849..4e67da54e 100644 --- a/apps/frontend/app/(client)/(code-editor)/_components/EditorLayout.tsx +++ b/apps/frontend/app/(client)/(code-editor)/_components/EditorLayout.tsx @@ -1,21 +1,14 @@ import ContestStatusTimeDiff from '@/components/ContestStatusTimeDiff' import HeaderAuthPanel from '@/components/auth/HeaderAuthPanel' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger -} from '@/components/shadcn/dropdown-menu' import { auth } from '@/libs/auth' -import { cn, convertToLetter, fetcher, fetcherWithAuth } from '@/libs/utils' -import checkIcon from '@/public/icons/check-green.svg' +import { fetcher, fetcherWithAuth } from '@/libs/utils' import codedangLogo from '@/public/logos/codedang-editor.svg' -import type { Contest, ContestProblem, ProblemDetail } from '@/types/type' +import type { Contest, ProblemDetail } from '@/types/type' import type { Route } from 'next' import Image from 'next/image' import Link from 'next/link' import { redirect } from 'next/navigation' -import { FaSortDown } from 'react-icons/fa' +import ContestProblemDropdown from './ContestProblemDropdown' import EditorMainResizablePanel from './EditorResizablePanel' interface EditorLayoutProps { @@ -24,25 +17,16 @@ interface EditorLayoutProps { children: React.ReactNode } -interface ContestProblemProps { - data: ContestProblem[] - total: number -} - export default async function EditorLayout({ contestId, problemId, children }: EditorLayoutProps) { - let problems: ContestProblemProps | undefined let contest: Contest | undefined let problem: ProblemDetail if (contestId) { // for getting contest info and problems list - problems = await fetcherWithAuth - .get(`contest/${contestId}/problem?take=20`) - .json() const res = await fetcherWithAuth( `contest/${contestId}/problem/${problemId}` ) @@ -72,46 +56,17 @@ export default async function EditorLayout({
{contest ? <>Contest : Problem}

/

- {contest ? ( + {contest && contestId ? ( <> {contest.title}

/

- - -

{`${convertToLetter(problems?.data.find((item) => item.id === Number(problemId))?.order as number)}. ${problem.title}`}

- -
- - {problems?.data.map((p: ContestProblem) => ( - - - {`${convertToLetter(p.order)}. ${p.title}`} - {p.submissionTime && ( -
- check -
- )} -
- - ))} -
-
+ ) : (

{`#${problem.id}. ${problem.title}`}

diff --git a/apps/frontend/stores/editor.ts b/apps/frontend/stores/editor.ts index d80aedc3a..699ebc086 100644 --- a/apps/frontend/stores/editor.ts +++ b/apps/frontend/stores/editor.ts @@ -23,6 +23,7 @@ export const useLanguageStore = (problemId: number, contestId?: number) => { ) ) } + interface CodeState { code: string setCode: (code: string) => void