Skip to content

Commit

Permalink
feat(fe): enable export contest overall excel V2 (#2210)
Browse files Browse the repository at this point in the history
* feat(fe): add react-csv

* feat(fe): add major

* feat(fe): enabel excel export

* feat(fe): pnpm add react-csv

* feat(fe): edit import queries

* feat(fe): change logo

* feat(fe): fix  build error

* feat(fe): check coding conventions

* feat(fe): check coding conventions

* feat(fe): delete unused file

* feat(fe): fix icon hoover
  • Loading branch information
Kimhyojung0810 authored Nov 16, 2024
1 parent 91466cf commit 7cb4f7a
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import ContestOverallTabs from '../_components/ContestOverallTabs'

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

Expand Down Expand Up @@ -58,7 +60,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} />
<ContestOverallTabs contestId={contestId} userId={userId} />
{tabs}
</main>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,163 @@
'use client'

import {
GET_CONTEST_SCORE_SUMMARIES,
GET_CONTEST_SUBMISSION_SUMMARIES_OF_USER,
GET_CONTESTS
} from '@/graphql/contest/queries'
import { cn } from '@/lib/utils'
import type { Route } from 'next'
import excelIcon from '@/public/icons/excel.svg'
import { useQuery } from '@apollo/client'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { CSVLink } from 'react-csv'

interface ScoreSummary {
realName: string
studentId: string
userContestScore: number
contestPerfectScore: number
submittedProblemCount: number
totalProblemCount: number
problemScores: { problemId: number; score: number; maxScore: number }[]
major: string
}
interface SubmissionSummary {
problemId: number
}

export default function ContestOverallTabs({
contestId
contestId,
userId
}: {
contestId: string
userId: number
}) {
const id = contestId
const id = parseInt(contestId, 10)
const pathname = usePathname()

const isCurrentTab = (tab: string) => {
if (tab === '') return pathname === `/admin/contest/${id}`
return pathname.startsWith(`/admin/contest/${id}/${tab}`)
}
const { data: scoreData } = useQuery<{
getContestScoreSummaries: ScoreSummary[]
}>(GET_CONTEST_SCORE_SUMMARIES, {
variables: { contestId: id, take: 300 },
skip: !contestId
})

useQuery<{
getContestSubmissionSummaryByUserId: { submissions: SubmissionSummary[] }
}>(GET_CONTEST_SUBMISSION_SUMMARIES_OF_USER, {
variables: { contestId: id, userId, take: 300 },
skip: !contestId || !userId
})

const { data: contestData } = useQuery(GET_CONTESTS, {
variables: { groupId: 1, take: 100 },
skip: !contestId
})

const contestTitle = contestData?.getContests.find(
(contest) => contest.id === contestId
)?.title

const fileName = contestTitle
? `${contestTitle.replace(/\s+/g, '_')}.csv`
: `contest-${id}-participants.csv`

const uniqueProblems = Array.from(
new Set(
scoreData?.getContestScoreSummaries.flatMap((user) =>
user.problemScores.map((score) => score.problemId)
) || []
)
)

const problemHeaders = uniqueProblems.flatMap((problemId, index) => {
const problemLabel = String.fromCharCode(65 + index)
return [
{
label: `${problemLabel}`,
key: `problems[${index}].maxScore`
}
]
})

const headers = [
{ label: '전공', key: 'major' },
{ label: '이름', key: 'realName' },
{ label: '학번', key: 'studentId' },
{ label: '총 획득 점수/총점', key: 'scoreRatio' },
{ label: '제출 문제 수/총 문제 수', key: 'problemRatio' },

...problemHeaders
]

const csvData =
scoreData?.getContestScoreSummaries.map((user) => {
const userProblemScores = uniqueProblems.map((problemId) => {
const scoreData = user.problemScores.find(
(ps) => ps.problemId === problemId
)

return {
maxScore: `${scoreData ? scoreData.score : 0}/${scoreData ? scoreData.maxScore : 0}`,
score: `${scoreData ? scoreData.score : 0}/${scoreData ? scoreData.maxScore : 0}`
}
})

return {
major: user.major,
realName: user.realName,
studentId: user.studentId,
scoreRatio: `${user.userContestScore}/${user.contestPerfectScore}`,
problemRatio:
user.submittedProblemCount === user.totalProblemCount
? 'Submit'
: `${user.submittedProblemCount}/${user.totalProblemCount}`,
problems: userProblemScores
}
}) || []

const isCurrentTab = (tab: string) =>
pathname.startsWith(`/admin/contest/${id}/${tab}`)

return (
<span className="flex w-max gap-1 rounded-lg bg-slate-200 p-1">
<Link
href={`/admin/contest/${id}` as Route}
className={cn(
'rounded-md px-3 py-1.5 text-lg font-semibold',
isCurrentTab('') && 'text-primary bg-white font-bold'
)}
>
Participant
</Link>
<Link
href={`/admin/contest/${id}/submission` as Route}
className={cn(
'rounded-md px-3 py-1.5 text-lg font-semibold',
isCurrentTab('submission') && 'text-primary bg-white font-bold'
)}
<div className="flex items-center justify-between">
<div className="flex w-max gap-1 rounded-lg bg-slate-200 p-1">
<Link
href={`/admin/contest/${id}`}
className={cn(
'rounded-md px-3 py-1.5 text-lg font-semibold',
isCurrentTab('') && 'text-primary bg-white font-bold'
)}
>
Participant
</Link>
<Link
href={`/admin/contest/${id}/submission`}
className={cn(
'rounded-md px-3 py-1.5 text-lg font-semibold',
isCurrentTab('submission') && 'text-primary bg-white font-bold'
)}
>
All Submission
</Link>
</div>
<CSVLink
data={csvData}
headers={headers}
filename={fileName}
className="flex items-center gap-2 rounded-lg bg-blue-400 px-3 py-1.5 text-lg font-semibold text-white transition-opacity hover:opacity-85"
>
All Submission
</Link>
</span>
Export
<Image
src={excelIcon}
alt="Excel Icon"
width={20}
height={20}
className="ml-1"
/>
</CSVLink>
</div>
)
}
1 change: 1 addition & 0 deletions apps/frontend/graphql/contest/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const GET_CONTEST_SCORE_SUMMARIES =
username
studentId
realName
major
}
}`)

Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"pretendard": "^1.3.9",
"react": "^18.3.1",
"react-circular-progressbar": "^2.1.0",
"react-csv": "^2.2.2",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.2",
Expand All @@ -97,6 +98,7 @@
"@types/node": "^20.17.6",
"@types/react": "^18.3.12",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-csv": "^1.1.10",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
Expand Down
10 changes: 10 additions & 0 deletions apps/frontend/public/icons/excel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 7cb4f7a

Please sign in to comment.