Skip to content

Commit

Permalink
Merge branch 'main' into t1080-apply-class-validator-on-user-tc
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaehyeon1020 authored Nov 24, 2024
2 parents d79e460 + 6ab60c7 commit 75186ed
Show file tree
Hide file tree
Showing 18 changed files with 259 additions and 120 deletions.
Original file line number Diff line number Diff line change
@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger className="flex gap-1 text-lg text-white outline-none">
<h1>{`${convertToLetter(contestProblems?.data.find((item) => item.id === Number(problemId))?.order as number)}. ${problem.title}`}</h1>
<FaSortDown />
</DropdownMenuTrigger>
<DropdownMenuContent className="border-slate-700 bg-slate-900">
{contestProblems?.data.map((p) => (
<Link key={p.id} href={`/contest/${contestId}/problem/${p.id}`}>
<DropdownMenuItem
className={cn(
'flex justify-between text-white hover:cursor-pointer focus:bg-slate-800 focus:text-white',
problem.id === p.id &&
'text-primary-light focus:text-primary-light'
)}
>
{`${convertToLetter(p.order)}. ${p.title}`}
{p.submissionTime && (
<div className="flex items-center justify-center pl-2">
<Image src={checkIcon} alt="check" width={16} height={16} />
</div>
)}
</DropdownMenuItem>
</Link>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -79,10 +80,12 @@ export default function Editor({
)
const { currentModal, showSignIn } = useAuthModalStore((state) => state)
const [showModal, setShowModal] = useState<boolean>(false)
const pushed = useRef(false)
//const pushed = useRef(false)
const whereToPush = useRef('')
const isModalConfrimed = useRef(false)

const queryClient = useQueryClient()

useInterval(
async () => {
const res = await fetcherWithAuth(`submission/${submissionId}`, {
Expand All @@ -99,7 +102,7 @@ export default function Editor({
? `/contest/${contestId}/problem/${problem.id}/submission/${submissionId}`
: `/problem/${problem.id}/submission/${submissionId}`
router.replace(href as Route)
window.history.pushState(null, '', '')
//window.history.pushState(null, '', window.location.href)
if (submission.result === 'Accepted') {
confetti?.addConfetti()
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -243,22 +249,26 @@ export default function Editor({
contestId
)

const handlePopState = () => {
if (!checkSaved()) {
whereToPush.current = contestId ? `/contest/${contestId}` : '/problem'
setShowModal(true)
} else window.history.back()
}
if (!pushed.current) {
window.history.pushState(null, '', '')
pushed.current = true
}
// TODO: 배포 후 뒤로 가기 로직 재구현

// const handlePopState = () => {
// if (!checkSaved()) {
// whereToPush.current = contestId
// ? `/contest/${contestId}/problem`
// : '/problem'
// setShowModal(true)
// } else window.history.back()
// }
// if (!pushed.current) {
// window.history.pushState(null, '', window.location.href)
// pushed.current = true
// }
window.addEventListener('beforeunload', handleBeforeUnload)
window.addEventListener('popstate', handlePopState)
//window.addEventListener('popstate', handlePopState)

return () => {
window.removeEventListener('beforeunload', handleBeforeUnload)
window.removeEventListener('popstate', handlePopState)
//window.removeEventListener('popstate', handlePopState)
}
}, [])

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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}`
)
Expand Down Expand Up @@ -72,46 +56,17 @@ export default async function EditorLayout({
<div className="flex items-center gap-1 font-medium">
{contest ? <>Contest</> : <Link href="/problem">Problem</Link>}
<p className="mx-2"> / </p>
{contest ? (
{contest && contestId ? (
<>
<Link href={`/contest/${contestId}` as Route}>
{contest.title}
</Link>
<p className="mx-2"> / </p>
<DropdownMenu>
<DropdownMenuTrigger className="flex gap-1 text-lg text-white outline-none">
<h1>{`${convertToLetter(problems?.data.find((item) => item.id === Number(problemId))?.order as number)}. ${problem.title}`}</h1>
<FaSortDown />
</DropdownMenuTrigger>
<DropdownMenuContent className="border-slate-700 bg-slate-900">
{problems?.data.map((p: ContestProblem) => (
<Link
key={p.id}
href={`/contest/${contestId}/problem/${p.id}` as Route}
>
<DropdownMenuItem
className={cn(
'flex justify-between text-white hover:cursor-pointer focus:bg-slate-800 focus:text-white',
problem.id === p.id &&
'text-primary-light focus:text-primary-light'
)}
>
{`${convertToLetter(p.order)}. ${p.title}`}
{p.submissionTime && (
<div className="flex items-center justify-center pl-2">
<Image
src={checkIcon}
alt="check"
width={16}
height={16}
/>
</div>
)}
</DropdownMenuItem>
</Link>
))}
</DropdownMenuContent>
</DropdownMenu>
<ContestProblemDropdown
problem={problem}
problemId={problemId}
contestId={contestId}
/>
</>
) : (
<h1 className="w-[1024px] overflow-hidden text-ellipsis whitespace-nowrap text-lg font-medium text-white">{`#${problem.id}. ${problem.title}`}</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ export default function TestcasePanel() {
.concat(result)
.filter(
(item, index, self) =>
index === self.findIndex((t) => t.id === item.id)
index === self.findIndex((t) => t.originalId === item.originalId)
)
)
setCurrentTab(result.id)
setCurrentTab(result.originalId)
}

const removeTab = (testcaseId: number) => {
setTestcaseTabList((state) =>
state.filter((item) => item.id !== testcaseId)
state.filter((item) => item.originalId !== testcaseId)
)
if (currentTab === testcaseId) {
setCurrentTab(0)
Expand Down Expand Up @@ -56,7 +56,7 @@ export default function TestcasePanel() {
<TestcaseTab
currentTab={currentTab}
onClickTab={() => setCurrentTab(0)}
nextTab={testcaseTabList[0]?.id}
nextTab={testcaseTabList[0]?.originalId}
className="flex-shrink-0"
>
{testcaseTabList.length < 7 ? 'Testcase Result' : 'TC Res'}
Expand All @@ -72,12 +72,12 @@ export default function TestcasePanel() {
{testcaseTabList.map((testcase, index) => (
<TestcaseTab
currentTab={currentTab}
prevTab={testcaseTabList[index - 1]?.id}
nextTab={testcaseTabList[index + 1]?.id}
onClickTab={() => setCurrentTab(testcase.id)}
onClickCloseButton={() => removeTab(testcase.id)}
testcaseId={testcase.id}
key={testcase.id}
prevTab={testcaseTabList[index - 1]?.originalId}
nextTab={testcaseTabList[index + 1]?.originalId}
onClickTab={() => setCurrentTab(testcase.originalId)}
onClickCloseButton={() => removeTab(testcase.originalId)}
testcaseId={testcase.originalId}
key={testcase.originalId}
>
{
(testcaseTabList.length < 7
Expand Down Expand Up @@ -117,7 +117,7 @@ export default function TestcasePanel() {
</div>
) : (
<TestResultDetail
data={processedData.find((item) => item.id === currentTab)}
data={processedData.find((item) => item.originalId === currentTab)}
/>
)}
</ScrollArea>
Expand Down Expand Up @@ -200,9 +200,9 @@ function TestSummary({
const total = data.length

const notAcceptedTestcases = data
.map((testcase, index) =>
.map((testcase) =>
testcase.result !== 'Accepted'
? `${testcase.isUserTestcase ? 'User' : 'Sample'} #${index + 1}`
? `${testcase.isUserTestcase ? 'User' : 'Sample'} #${testcase.id}`
: -1
)
.filter((index) => index !== -1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,20 @@ export const useTestResults = () => {
})

const testcases = useTestcaseStore((state) => state.getTestcases())
let userTestcaseCount = 1
let sampleTestcaseCount = 1
const testResults =
data.length > 0
? testcases.map((testcase, index) => {
const testResult = data.find((item) => item.id === testcase.id)
if (testcase.isUserTestcase) {
testcase.id = userTestcaseCount++
} else {
testcase.id = sampleTestcaseCount++
}
return {
id: index + 1,
originalId: testcase.id,
id: testcase.id,
originalId: index + 1,
input: testcase.input,
expectedOutput: testcase.output,
output: testResult?.output ?? '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import DataTablePagination from '@/app/admin/_components/table/DataTablePaginati
import DataTableProblemFilter from '@/app/admin/_components/table/DataTableProblemFilter'
import DataTableRoot from '@/app/admin/_components/table/DataTableRoot'
import DataTableSearchBar from '@/app/admin/_components/table/DataTableSearchBar'
import SubmissionDetailAdmin from '@/app/admin/contest/[contestId]/_components/SubmissionDetailAdmin'
import { Dialog, DialogContent } from '@/components/shadcn/dialog'
import { GET_CONTEST_SUBMISSIONS } from '@/graphql/submission/queries'
import { useSuspenseQuery } from '@apollo/client'
import { useState } from 'react'
import { columns } from './Columns'
import SubmissionDetailAdmin from './SubmissionDetailAdmin'

export function SubmissionTable({ contestId }: { contestId: number }) {
const { data } = useSuspenseQuery(GET_CONTEST_SUBMISSIONS, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@ import ContestOverallTabs from '../_components/ContestOverallTabs'

export default function Layout({
params,
tabs,
userId
tabs
}: {
params: { contestId: string }
tabs: React.ReactNode
userId: number
}) {
const { contestId } = params

Expand Down Expand Up @@ -60,7 +58,7 @@ export default function Layout({
content={contestData?.description}
classname="prose mb-4 w-full max-w-full border-y-2 border-y-gray-300 p-5 py-12"
/>
<ContestOverallTabs contestId={contestId} userId={userId} />
<ContestOverallTabs contestId={contestId} />
{tabs}
</main>
)
Expand Down
Loading

0 comments on commit 75186ed

Please sign in to comment.