-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fe): add admin problem detail (#1524)
* feat(fe): add problem detail layout * feat(fe): add submission table * feat(fe): add username filter input * feat(fe): add filter for userID Search * fix: change user to username, change id to underscore * fix: edit datatable input placeholder * fix: delete files of frontend-client --------- Co-authored-by: jiho <[email protected]>
- Loading branch information
1 parent
634e1b4
commit 5fb21d1
Showing
6 changed files
with
962 additions
and
666 deletions.
There are no files selected for viewing
59 changes: 59 additions & 0 deletions
59
apps/frontend/app/admin/problem/[id]/_components/Columns.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
'use client' | ||
|
||
import type { SubmissionItem } from '@/types/type' | ||
import type { ColumnDef } from '@tanstack/react-table' | ||
import dayjs from 'dayjs' | ||
|
||
export const columns: ColumnDef<SubmissionItem>[] = [ | ||
{ | ||
header: '#', | ||
accessorKey: 'id', | ||
cell: ({ row }) => <p className="text-sm">{row.original.id}</p> | ||
}, | ||
{ | ||
id: 'username', | ||
header: () => 'User ID', | ||
accessorKey: 'username', | ||
cell: ({ row }) => row.original.user.username, | ||
// submission userID Search Filter | ||
filterFn: (row, _, value) => { | ||
const users = row.original.user | ||
return users.username.includes(value) | ||
} | ||
}, | ||
{ | ||
header: () => 'Result', | ||
accessorKey: 'result', | ||
cell: ({ row }) => { | ||
return row.original.result === 'Accepted' ? ( | ||
<p className="text-green-500">{row.original.result}</p> | ||
) : row.original.result === 'Judging' ? ( | ||
<p className="text-gray-500">{row.original.result}</p> | ||
) : ( | ||
<p className="text-red-500">{row.original.result}</p> | ||
) | ||
} | ||
}, | ||
{ | ||
header: () => 'Language', | ||
accessorKey: 'language', | ||
cell: ({ row }) => row.original.language | ||
}, | ||
{ | ||
header: () => 'Submission Time', | ||
accessorKey: 'createTime', | ||
cell: ({ row }) => | ||
dayjs(row.original.createTime).format('YYYY-MM-DD HH:mm:ss') | ||
}, | ||
{ | ||
header: () => 'Code Size', | ||
accessorKey: 'codeSize', | ||
cell: ({ row }) => { | ||
return row.original.codeSize === null ? ( | ||
<p>N/A</p> | ||
) : ( | ||
<p>{row.original.codeSize} B</p> | ||
) | ||
} | ||
} | ||
] |
169 changes: 169 additions & 0 deletions
169
apps/frontend/app/admin/problem/[id]/_components/DataTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
'use client' | ||
|
||
import { Input } from '@/components/ui/input' | ||
import { | ||
Table, | ||
TableBody, | ||
TableCell, | ||
TableHead, | ||
TableHeader, | ||
TableRow | ||
} from '@/components/ui/table' | ||
import { cn } from '@/lib/utils' | ||
import type { ColumnDef } from '@tanstack/react-table' | ||
import { | ||
flexRender, | ||
getCoreRowModel, | ||
getFilteredRowModel, | ||
useReactTable | ||
} from '@tanstack/react-table' | ||
import type { Route } from 'next' | ||
import Link from 'next/link' | ||
import { useRouter } from 'next/navigation' | ||
|
||
interface DataTableProps<TData, TValue> { | ||
columns: ColumnDef<TData, TValue>[] | ||
data: TData[] | ||
headerStyle: { | ||
[key: string]: string | ||
} | ||
problemId: number | ||
} | ||
|
||
/** | ||
* @param columns | ||
* columns to be displayed | ||
* @param data | ||
* data to be displayed | ||
* @param headerStyle | ||
* tailwindcss class name for each header | ||
* @param name | ||
* name of the table, used for routing | ||
* @example | ||
* ```tsx | ||
* // page.tsx | ||
* <DataTable data={data} columns={columns} headerStyle={{ | ||
* title: 'text-left w-2/4 md:w-4/6', | ||
* createdBy: 'w-1/4 md:w-1/6', | ||
* createTime: 'w-1/4 md:w-1/6' | ||
* }} | ||
* name="notice" | ||
* /> | ||
* ``` | ||
* ```tsx | ||
* // _components/Columns.tsx | ||
* import type { Notice } from '@/types/type' | ||
* export const columns: ColumnDef<Notice, string>[] = [ | ||
* { | ||
* header: 'Title', | ||
* accessorKey: 'title', | ||
* cell: (row) => row.original.title, | ||
* }, | ||
* ... | ||
* ] | ||
* ``` | ||
*/ | ||
|
||
interface Item { | ||
id: number | ||
} | ||
|
||
export default function DataTable<TData extends Item, TValue>({ | ||
columns, | ||
data, | ||
headerStyle, | ||
problemId | ||
}: DataTableProps<TData, TValue>) { | ||
const table = useReactTable({ | ||
data, | ||
columns, | ||
getCoreRowModel: getCoreRowModel(), | ||
getFilteredRowModel: getFilteredRowModel() | ||
}) | ||
const router = useRouter() | ||
|
||
return ( | ||
<div> | ||
<Input | ||
placeholder="Search User" | ||
value={(table.getColumn('username')?.getFilterValue() as string) ?? ''} | ||
onChange={(event) => { | ||
table.getColumn('username')?.setFilterValue(event.target.value) | ||
}} | ||
className="h-10 w-[150px] lg:w-[250px]" | ||
/> | ||
<Table className="table-fixed"> | ||
<TableHeader> | ||
{table.getHeaderGroups().map((headerGroup) => ( | ||
<TableRow className="hover:bg-gray-200/40" key={headerGroup.id}> | ||
{headerGroup.headers.map((header) => { | ||
return ( | ||
<TableHead | ||
key={header.id} | ||
className={cn( | ||
'border-b border-gray-400 text-center text-xs font-semibold', | ||
headerStyle[header.id] | ||
)} | ||
style={{ padding: 0 }} | ||
> | ||
{header.isPlaceholder | ||
? null | ||
: flexRender( | ||
header.column.columnDef.header, | ||
header.getContext() | ||
)} | ||
</TableHead> | ||
) | ||
})} | ||
</TableRow> | ||
))} | ||
</TableHeader> | ||
<TableBody> | ||
{table.getRowModel().rows?.length ? ( | ||
table.getRowModel().rows.map((row) => { | ||
const href = | ||
`/problem/${problemId}/submission/${row.original.id}` as Route | ||
return ( | ||
<TableRow | ||
key={row.id} | ||
data-state={row.getIsSelected() && 'selected'} | ||
className="cursor-pointer border-t border-gray-400 hover:bg-gray-200/40 hover:font-semibold" | ||
onClick={() => { | ||
router.push(href) | ||
}} | ||
> | ||
{row.getVisibleCells().map((cell) => ( | ||
<TableCell | ||
key={cell.id} | ||
style={{ | ||
paddingTop: 10, | ||
paddingBottom: 10, | ||
paddingLeft: 2, | ||
paddingRight: 2 | ||
}} | ||
> | ||
<div className="text-center text-xs"> | ||
{flexRender( | ||
cell.column.columnDef.cell, | ||
cell.getContext() | ||
)} | ||
</div> | ||
{/* for prefetch */} | ||
<Link href={href} /> | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
) | ||
}) | ||
) : ( | ||
<TableRow className="hover:bg-gray-200/40"> | ||
<TableCell colSpan={columns.length} className="h-24 text-center"> | ||
No results. | ||
</TableCell> | ||
</TableRow> | ||
)} | ||
</TableBody> | ||
</Table> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.