Skip to content

Commit

Permalink
refactor(fe): replace data table admin component to new components (1) (
Browse files Browse the repository at this point in the history
#2176)

* refactor(fe): refactor admin user table

* refactor(fe): refactor admin problem table

* refactor(fe): refactor admin contest table

* refactor(fe): refactor contest problem table and import problem table

* chore(fe): fix admin page layout

* fix(fe): reset page index when the filter is changed

* chore(fe): add error toast message

* chore(fe): separate component file

* fix(fe): force to trigger sorting fn when the row selection is changed
  • Loading branch information
eunnbi authored Nov 3, 2024
1 parent e5d8163 commit a8a928a
Show file tree
Hide file tree
Showing 23 changed files with 739 additions and 420 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Separator } from '@/components/ui/separator'
import type { Column } from '@tanstack/react-table'
import type { ReactNode } from 'react'
import { IoFilter } from 'react-icons/io5'
import { useDataTable } from './context'

interface DataTableMultiSelectFilterProps<TData, TValue> {
column?: Column<TData, TValue>
Expand Down Expand Up @@ -45,6 +46,7 @@ export default function DataTableMultiSelectFilter<TData, TValue>({
options,
emptyMessage
}: DataTableMultiSelectFilterProps<TData, TValue>) {
const { table } = useDataTable()
const selectedValues = getSelectedValues(column?.getFilterValue())

return (
Expand Down Expand Up @@ -109,6 +111,7 @@ export default function DataTableMultiSelectFilter<TData, TValue>({
column?.setFilterValue(
filterValues.length ? filterValues : undefined
)
table.resetPageIndex()
}}
>
<Checkbox checked={selectedValues.has(value)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export default function DataTableProblemFilter({
onSelect={() => {
column?.setFilterValue(value)
setOpen(false)
table.resetPageIndex()
}}
>
<p className="overflow-hidden text-ellipsis whitespace-nowrap text-xs">
Expand Down
36 changes: 19 additions & 17 deletions apps/frontend/app/admin/contest/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import DescriptionForm from '@/app/admin/_components/DescriptionForm'
import FormSection from '@/app/admin/_components/FormSection'
import SwitchField from '@/app/admin/_components/SwitchField'
import TitleForm from '@/app/admin/_components/TitleForm'
import { DataTableAdmin } from '@/components/DataTableAdmin'
import {
AlertDialog,
AlertDialogTrigger,
Expand Down Expand Up @@ -40,16 +39,19 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { PlusCircleIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useRef, useState } from 'react'
import { Suspense, useRef, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { FaAngleLeft } from 'react-icons/fa6'
import { IoIosCheckmarkCircle } from 'react-icons/io'
import { toast } from 'sonner'
import ContestProblemListLabel from '../../_components/ContestProblemListLabel'
import ImportProblemTable from '../../_components/ImportProblemTable'
import ContestProblemTable from '../../_components/ContestProblemTable'
import {
ImportProblemTable,
ImportProblemTableFallback
} from '../../_components/ImportProblemTable'
import TimeForm from '../../_components/TimeForm'
import { type ContestProblem, editSchema } from '../../utils'
import { columns } from '../_components/Columns'

export default function Page({ params }: { params: { id: string } }) {
const [prevProblemIds, setPrevProblemIds] = useState<number[]>([])
Expand Down Expand Up @@ -305,22 +307,22 @@ export default function Page({ params }: { params: { id: string } }) {
<DialogHeader>
<DialogTitle>Import Problem</DialogTitle>
</DialogHeader>
<ImportProblemTable
checkedProblems={problems as ContestProblem[]}
onSelectedExport={(problems) =>
setProblems(problems as ContestProblem[])
}
onCloseDialog={() => setShowImportDialog(false)}
/>
<Suspense fallback={<ImportProblemTableFallback />}>
<ImportProblemTable
checkedProblems={problems}
onSelectedExport={(problems) => {
setProblems(problems)
setShowImportDialog(false)
}}
/>
</Suspense>
</DialogContent>
</Dialog>
</div>
<DataTableAdmin
columns={columns(problems, setProblems, hasSubmission)}
data={problems as ContestProblem[]}
defaultSortColumn={{ id: 'order', desc: false }}
enableFooter={true}
defaultPageSize={20}
<ContestProblemTable
problems={problems}
setProblems={setProblems}
disableInput={hasSubmission}
/>
</div>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,12 @@ import ContainedContests from '@/app/admin/problem/_components/ContainedContests
import OptionSelect from '@/components/OptionSelect'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { GET_BELONGED_CONTESTS } from '@/graphql/contest/queries'
import type { Level } from '@/types/type'
import { useQuery } from '@apollo/client'
import type { ColumnDef, Row } from '@tanstack/react-table'
import type { ColumnDef } from '@tanstack/react-table'
import { toast } from 'sonner'
import type { ContestProblem } from '../../utils'
import type { ContestProblem } from '../utils'

function Included({ row }: { row: Row<ContestProblem> }) {
const contestData = useQuery(GET_BELONGED_CONTESTS, {
variables: {
problemId: Number(row.original.id)
}
}).data
return (
<div className="flex justify-center">
{contestData && <ContainedContests data={contestData} />}
</div>
)
}

export const columns = (
problems: ContestProblem[],
export const createColumns = (
setProblems: React.Dispatch<React.SetStateAction<ContestProblem[]>>,
disableInput: boolean
): ColumnDef<ContestProblem>[] => [
Expand Down Expand Up @@ -103,15 +87,15 @@ export const columns = (
/>
</div>
),
footer: () => (
footer: ({ table }) => (
<div className="flex justify-center">
<Input
disabled={true}
className="w-[70px] focus-visible:ring-0"
defaultValue={problems.reduce(
(total, problem) => total + problem.score,
0
)}
defaultValue={table
.getCoreRowModel()
.rows.map((row) => row.original)
.reduce((total, problem) => total + problem.score, 0)}
/>
</div>
),
Expand Down Expand Up @@ -180,7 +164,11 @@ export const columns = (
{
accessorKey: 'included',
header: () => <p className="text-center text-sm">Included</p>,
cell: ({ row }) => <Included row={row} />,
cell: ({ row }) => (
<div className="flex justify-center">
<ContainedContests problemId={row.original.id} />
</div>
),
enableSorting: false
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useMemo, type Dispatch, type SetStateAction } from 'react'
import DataTable from '../../_components/table/DataTable'
import DataTableRoot from '../../_components/table/DataTableRoot'
import type { ContestProblem } from '../utils'
import { createColumns } from './ContestProblemColumns'

interface ContestProblemTableProps {
problems: ContestProblem[]
setProblems: Dispatch<SetStateAction<ContestProblem[]>>
disableInput: boolean
}

export default function ContestProblemTable({
problems,
setProblems,
disableInput
}: ContestProblemTableProps) {
const columns = useMemo(
() => createColumns(setProblems, disableInput),
[setProblems, disableInput]
)

return (
<DataTableRoot
columns={columns}
data={problems}
defaultPageSize={20}
defaultSortState={[{ id: 'order', desc: false }]}
>
<DataTable showFooter={true} />
</DataTableRoot>
)
}
86 changes: 86 additions & 0 deletions apps/frontend/app/admin/contest/_components/ContestTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use client'

import { DELETE_CONTEST } from '@/graphql/contest/mutations'
import { GET_CONTESTS } from '@/graphql/contest/queries'
import { useApolloClient, useMutation, useSuspenseQuery } from '@apollo/client'
import DataTable from '../../_components/table/DataTable'
import DataTableDeleteButton from '../../_components/table/DataTableDeleteButton'
import DataTableFallback from '../../_components/table/DataTableFallback'
import DataTablePagination from '../../_components/table/DataTablePagination'
import DataTableRoot from '../../_components/table/DataTableRoot'
import DataTableSearchBar from '../../_components/table/DataTableSearchBar'
import { columns } from './ContestTableColumns'
import DuplicateContestButton from './DuplicateContestButton'

const headerStyle = {
select: '',
title: 'w-3/5',
startTime: 'px-0 w-1/5',
participants: 'px-0 w-1/12',
isVisible: 'px-0 w-1/12'
}

export function ContestTable() {
const { data } = useSuspenseQuery(GET_CONTESTS, {
variables: {
groupId: 1,
take: 300
}
})

const contests = data.getContests.map((contest) => ({
...contest,
id: Number(contest.id)
}))

return (
<DataTableRoot
data={contests}
columns={columns}
defaultSortState={[{ id: 'startTime', desc: true }]}
>
<div className="flex gap-2">
<DataTableSearchBar columndId="title" />
<DuplicateContestButton />
<ContestsDeleteButton />
</div>
<DataTable
headerStyle={headerStyle}
getHref={(data) => `/admin/contest/${data.id}`}
/>
<DataTablePagination showSelection />
</DataTableRoot>
)
}

function ContestsDeleteButton() {
const client = useApolloClient()
const [deleteContest] = useMutation(DELETE_CONTEST)

const deleteTarget = (id: number) => {
return deleteContest({
variables: {
groupId: 1,
contestId: id
}
})
}

const onSuccess = () => {
client.refetchQueries({
include: [GET_CONTESTS]
})
}

return (
<DataTableDeleteButton
target="contest"
deleteTarget={deleteTarget}
onSuccess={onSuccess}
/>
)
}

export function ContestTableFallback() {
return <DataTableFallback columns={columns} headerStyle={headerStyle} />
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { DataTableColumnHeader } from '@/components/DataTableColumnHeader'
import DataTableColumnHeader from '@/app/admin/_components/table/DataTableColumnHeader'
import { Checkbox } from '@/components/ui/checkbox'
import { Switch } from '@/components/ui/switch'
import {
Expand All @@ -19,7 +19,7 @@ import type { ColumnDef, Row } from '@tanstack/react-table'
import Image from 'next/image'
import { toast } from 'sonner'

interface DataTableContest {
export interface DataTableContest {
id: number
title: string
startTime: string
Expand Down Expand Up @@ -77,6 +77,7 @@ function VisibleCell({ row }: { row: Row<DataTableContest> }) {
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={(e) => e.stopPropagation()}
className="h-6 w-6"
>
Expand Down
Loading

0 comments on commit a8a928a

Please sign in to comment.