Skip to content

Commit

Permalink
feat(fe): show alert modal after editting problem score (#2209)
Browse files Browse the repository at this point in the history
* feat(fe): add score caution modal

* chore(fe): apply belonged contest queries update

* feat(fe): create set to zero button

* feat(fe): create revert score button

* feat(fe): apply update contest problems score

* feat(fe): remove create problem alert dialog

* feat(fe): remove create contest alert dialog

* chore(fe): resolve reviews
  • Loading branch information
jwoojin9 authored Nov 16, 2024
1 parent b68322c commit 452f739
Show file tree
Hide file tree
Showing 11 changed files with 483 additions and 155 deletions.
33 changes: 2 additions & 31 deletions apps/frontend/app/admin/contest/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export default function Page() {
const [problems, setProblems] = useState<ContestProblem[]>([])
const [isCreating, setIsCreating] = useState<boolean>(false)
const [showImportDialog, setShowImportDialog] = useState<boolean>(false)
const [showCreateModal, setShowCreateModal] = useState(false)

const shouldSkipWarning = useRef(false)
const router = useRouter()
Expand Down Expand Up @@ -90,7 +89,7 @@ export default function Page() {
toast.error('Duplicate problem order found')
return
}
setShowCreateModal(true)
onSubmit()
}

const onSubmit = async () => {
Expand Down Expand Up @@ -253,39 +252,11 @@ export default function Page() {
<Button
type="submit"
className="flex h-[36px] w-[100px] items-center gap-2 px-0"
disabled={isCreating}
>
<IoMdCheckmarkCircleOutline fontSize={20} />
<div className="mb-[2px] text-base">Create</div>
</Button>
<AlertDialog open={showCreateModal}>
<AlertDialogContent className="p-8">
<AlertDialogHeader className="gap-2">
<AlertDialogTitle>Create Contest?</AlertDialogTitle>
<AlertDialogDescription>
Once user submit any coding, the contest problem list and
score <span className="underline">cannot</span> be modified.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
type="button"
className="rounded-md px-4 py-2"
onClick={() => setShowCreateModal(false)}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
type="button"
disabled={isCreating}
onClick={() => onSubmit()}
>
Create
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</FormProvider>
</form>
</main>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client'

import DataTable from '@/app/admin/_components/table/DataTable'
import DataTableFallback from '@/app/admin/_components/table/DataTableFallback'
import DataTableRoot from '@/app/admin/_components/table/DataTableRoot'
import { GET_BELONGED_CONTESTS } from '@/graphql/contest/queries'
import { useSuspenseQuery } from '@apollo/client'
import { useEffect, useState } from 'react'
import { columns, type BelongedContest } from './BelongedContestTableColumns'
import RevertScoreButton from './RevertScoreButton'
import SetToZeroButton from './SetToZeroButton'

export function BelongedContestTable({
problemId,
onSetToZero,
onRevertScore
}: {
problemId: number
onSetToZero: (data: number[]) => void
onRevertScore: () => void
}) {
const [contests, setContests] = useState<BelongedContest[]>([])

const { data } = useSuspenseQuery(GET_BELONGED_CONTESTS, {
variables: {
problemId
}
})

useEffect(() => {
if (data) {
const mappedData: BelongedContest[] = [
...data.getContestsByProblemId.upcoming.map((contest) => ({
id: Number(contest.id),
title: contest.title,
state: 'Upcoming',
problemScore: contest.problemScore,
totalScore: contest.totalScore,
isSetToZero: false
})),
...data.getContestsByProblemId.ongoing.map((contest) => ({
id: Number(contest.id),
title: contest.title,
state: 'Ongoing',
problemScore: contest.problemScore,
totalScore: contest.totalScore,
isSetToZero: false
})),
...data.getContestsByProblemId.finished.map((contest) => ({
id: Number(contest.id),
title: contest.title,
state: 'Finished',
problemScore: contest.problemScore,
totalScore: contest.totalScore,
isSetToZero: false
}))
]
setContests(mappedData)
}
}, [data])

return (
<DataTableRoot data={contests} columns={columns}>
<DataTable />
<SetToZeroButton
onSetToZero={(contestsToSetZero) => {
setContests((contests) =>
contests.map((contest) =>
contestsToSetZero.includes(contest.id)
? { ...contest, isSetToZero: true }
: contest
)
)
onSetToZero(contestsToSetZero)
}}
/>
<RevertScoreButton
onRevertScore={() => {
setContests(
contests.map((contest) => ({ ...contest, isSetToZero: false }))
)
onRevertScore()
}}
/>
</DataTableRoot>
)
}

export function BelongedContestTableFallback() {
return <DataTableFallback withSearchBar={false} columns={columns} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use client'

import DataTableColumnHeader from '@/app/admin/_components/table/DataTableColumnHeader'
import { Checkbox } from '@/components/shadcn/checkbox'
import { cn } from '@/lib/utils'
import type { ColumnDef } from '@tanstack/react-table'

export interface BelongedContest {
id: number
title: string
state: string
problemScore: number
totalScore: number
isSetToZero: boolean
}

export const columns: ColumnDef<BelongedContest>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
onClick={(e) => e.stopPropagation()}
checked={table.getIsAllPageRowsSelected()}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
className="translate-y-[2px] bg-white"
/>
),
cell: ({ row }) => (
<Checkbox
onClick={(e) => e.stopPropagation()}
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
className="translate-y-[2px] bg-white"
/>
),
enableSorting: false,
enableHiding: false
},
{
accessorKey: 'title',
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Contest Title" />
),
cell: ({ row }) => (
<p className="max-w-[700px] overflow-hidden text-ellipsis whitespace-nowrap text-left font-medium">
{row.getValue('title')}
</p>
),
enableSorting: false,
enableHiding: false
},
{
accessorKey: 'state',
header: () => (
<p className="text-center font-mono text-sm font-medium">State</p>
),
cell: ({ row }) => (
<p className="text-center font-normal">{row.getValue('state')}</p>
)
},
{
accessorKey: 'problemScore',
header: () => (
<p className="text-center font-mono text-sm font-medium">Problem Score</p>
),
cell: ({ row }) => (
<p
className={cn(
'text-center font-normal',
row.original.isSetToZero && 'text-primary'
)}
>
{row.original.isSetToZero ? '0' : row.getValue('problemScore')}
</p>
)
},
{
accessorKey: 'totalScore',
header: () => (
<p className="text-center font-mono text-sm font-medium">Total Score</p>
),
cell: ({ row }) => (
<p
className={cn(
'text-center font-normal',
row.original.isSetToZero && 'text-primary'
)}
>
{row.original.isSetToZero ? '0' : row.getValue('problemScore')}/
{row.original.isSetToZero
? Number(row.getValue('totalScore')) -
Number(row.getValue('problemScore'))
: row.getValue('totalScore')}
</p>
)
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useDataTable } from '@/app/admin/_components/table/context'
import { Button } from '@/components/shadcn/button'
import type { BelongedContest } from './BelongedContestTableColumns'

interface SetToZeroButtonProps {
onRevertScore: () => void
}

export default function RevertScoreButton({
onRevertScore
}: SetToZeroButtonProps) {
const { table } = useDataTable<BelongedContest>()

const selectedContests = table.getSelectedRowModel().rows

return (
<>
{selectedContests.length > 0 && (
<Button
onClick={() => {
table.resetRowSelection()
onRevertScore()
}}
variant="filter"
className="ml-3"
>
Revert Score
</Button>
)}
</>
)
}
Loading

0 comments on commit 452f739

Please sign in to comment.