mirror of
https://github.com/ershisan99/www.git
synced 2026-01-05 21:02:08 +00:00
refactor user table, make opponent name a link
This commit is contained in:
228
src/app/players/[id]/_components/games-table.tsx
Normal file
228
src/app/players/[id]/_components/games-table.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
'use client'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { SelectGames } from '@/server/db/types'
|
||||
import {
|
||||
type SortingState,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import { ArrowDownCircle, ArrowUp, ArrowUpCircle } from 'lucide-react'
|
||||
import { useFormatter } from 'next-intl'
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
const numberFormatter = new Intl.NumberFormat('en-US', {
|
||||
signDisplay: 'exceptZero',
|
||||
})
|
||||
|
||||
const columnHelper = createColumnHelper<SelectGames>()
|
||||
|
||||
const useColumns = () => {
|
||||
const format = useFormatter()
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor('opponentName', {
|
||||
meta: { className: 'pl-4' },
|
||||
header: 'Opponent',
|
||||
cell: (info) => (
|
||||
<Link
|
||||
href={`/players/${info.row.original.opponentId}`}
|
||||
className='pl-4 font-medium hover:underline'
|
||||
>
|
||||
{info.getValue()}
|
||||
</Link>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('gameType', {
|
||||
header: 'Game Type',
|
||||
cell: (info) => {
|
||||
const gameType = info.getValue()
|
||||
return (
|
||||
<Badge
|
||||
variant='outline'
|
||||
className={cn(
|
||||
'font-normal capitalize',
|
||||
gameType === 'ranked'
|
||||
? 'border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-800 dark:bg-violet-950 dark:text-violet-300'
|
||||
: gameType.toLowerCase() === 'vanilla'
|
||||
? 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300'
|
||||
: 'border-zinc-200 bg-zinc-50 text-zinc-700 dark:border-zinc-800 dark:bg-zinc-700 dark:text-zinc-300'
|
||||
)}
|
||||
>
|
||||
{info.getValue()}
|
||||
</Badge>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('opponentMmr', {
|
||||
header: 'Opponent MMR',
|
||||
meta: { className: 'justify-end' },
|
||||
cell: (info) => (
|
||||
<span className='flex w-full justify-end font-mono'>
|
||||
{Math.trunc(info.getValue())}
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('playerMmr', {
|
||||
header: 'MMR',
|
||||
meta: { className: 'justify-end' },
|
||||
cell: (info) => (
|
||||
<span className='flex w-full justify-end font-mono'>
|
||||
{Math.trunc(info.getValue())}
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('mmrChange', {
|
||||
header: 'Result',
|
||||
meta: { className: 'justify-end' },
|
||||
cell: (info) => {
|
||||
const mmrChange = info.getValue()
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'flex items-center justify-end font-medium font-mono',
|
||||
mmrChange > 0 ? 'text-emerald-500' : 'text-rose-500'
|
||||
)}
|
||||
>
|
||||
{numberFormatter.format(Math.trunc(mmrChange))}
|
||||
{mmrChange > 0 ? (
|
||||
<ArrowUpCircle className='ml-1 h-4 w-4' />
|
||||
) : (
|
||||
<ArrowDownCircle className='ml-1 h-4 w-4' />
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor('gameTime', {
|
||||
header: 'Date',
|
||||
meta: { className: 'justify-end' },
|
||||
cell: (info) => (
|
||||
<span
|
||||
className={'flex items-center justify-end font-medium font-mono'}
|
||||
>
|
||||
{format.dateTime(info.getValue(), {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('gameTime', {
|
||||
header: 'Time',
|
||||
meta: { className: 'justify-end pr-4' },
|
||||
cell: (info) => (
|
||||
<span
|
||||
className={
|
||||
'flex items-center justify-end pr-4 font-medium font-mono'
|
||||
}
|
||||
>
|
||||
{format.dateTime(info.getValue(), {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</span>
|
||||
),
|
||||
id: 'time',
|
||||
}),
|
||||
],
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
export function GamesTable({ games }: { games: SelectGames[] }) {
|
||||
const [sorting, setSorting] = useState<SortingState>([])
|
||||
const columns = useColumns()
|
||||
const table = useReactTable({
|
||||
data: games,
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getRowId: (originalRow) => originalRow.gameNum.toString(),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='rounded-md border'>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const sortDirection = header.column.getIsSorted()
|
||||
return (
|
||||
<TableHead key={header.id} className={'px-0'}>
|
||||
<span
|
||||
className={cn(
|
||||
'flex w-full items-center',
|
||||
(header.column.columnDef.meta as any)?.className
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
className={cn(
|
||||
header.column.getCanSort() &&
|
||||
'cursor-pointer select-none',
|
||||
(
|
||||
header.column.columnDef.meta as any
|
||||
)?.className?.includes('justify-end') &&
|
||||
'flex-row-reverse'
|
||||
)}
|
||||
size={'table'}
|
||||
variant='ghost'
|
||||
onClick={header.column.getToggleSortingHandler()}
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
{sortDirection ? (
|
||||
<ArrowUp
|
||||
className={cn(
|
||||
'transition-transform',
|
||||
sortDirection === 'asc' ? 'rotate-180' : ''
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<div className={'h-4 w-4'} />
|
||||
)}
|
||||
</Button>
|
||||
</span>
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
import type React from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { GamesTable } from '@/app/players/[id]/_components/games-table'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
@@ -13,14 +14,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { RANKED_CHANNEL, VANILLA_CHANNEL } from '@/shared/constants'
|
||||
@@ -29,10 +22,8 @@ import {
|
||||
ArrowDownCircle,
|
||||
ArrowUpCircle,
|
||||
BarChart3,
|
||||
Calendar,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Clock,
|
||||
Filter,
|
||||
MinusCircle,
|
||||
Star,
|
||||
@@ -342,107 +333,7 @@ export function UserInfo() {
|
||||
<TabsContent value='matches' className='m-0'>
|
||||
<div className='overflow-hidden rounded-lg border'>
|
||||
<div className='overflow-x-auto'>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className='bg-gray-50 dark:bg-zinc-800/50'>
|
||||
<TableHead className='w-[100px]'>Game Type</TableHead>
|
||||
<TableHead>Opponent</TableHead>
|
||||
<TableHead className='text-right'>
|
||||
Opponent MMR
|
||||
</TableHead>
|
||||
<TableHead className='text-right'>MMR</TableHead>
|
||||
<TableHead className='text-right'>Result</TableHead>
|
||||
<TableHead className='text-center'>
|
||||
Leaderboard
|
||||
</TableHead>
|
||||
<TableHead className='text-right'>
|
||||
<span className='flex items-center justify-end gap-1'>
|
||||
<Calendar className='h-4 w-4' /> Date
|
||||
</span>
|
||||
</TableHead>
|
||||
<TableHead className='text-right'>
|
||||
<span className='flex items-center justify-end gap-1'>
|
||||
<Clock className='h-4 w-4' /> Time
|
||||
</span>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredGames.map((game) => (
|
||||
<TableRow
|
||||
key={game.gameId}
|
||||
className='transition-colors hover:bg-gray-50 dark:hover:bg-zinc-800/70'
|
||||
>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant='outline'
|
||||
className='font-normal capitalize'
|
||||
>
|
||||
{game.gameType}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className='font-medium'>
|
||||
{game.opponentName}
|
||||
</TableCell>
|
||||
<TableCell className='text-right font-mono'>
|
||||
{Math.trunc(game.opponentMmr)}
|
||||
</TableCell>
|
||||
<TableCell className='text-right font-mono'>
|
||||
{Math.trunc(game.playerMmr)}
|
||||
</TableCell>
|
||||
<TableCell className='text-right font-mono'>
|
||||
{game.mmrChange > 0 ? (
|
||||
<span className='flex items-center justify-end font-medium text-emerald-500'>
|
||||
{numberFormatter.format(
|
||||
Math.trunc(game.mmrChange)
|
||||
)}
|
||||
<ArrowUpCircle className='ml-1 inline h-4 w-4' />
|
||||
</span>
|
||||
) : (
|
||||
<span className='flex items-center justify-end font-medium text-rose-500'>
|
||||
{numberFormatter.format(
|
||||
Math.trunc(game.mmrChange)
|
||||
)}
|
||||
<ArrowDownCircle className='ml-1 inline h-4 w-4' />
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className='text-center'>
|
||||
<Badge
|
||||
variant='outline'
|
||||
className={cn(
|
||||
'w-full font-normal',
|
||||
game.gameType === 'ranked'
|
||||
? 'border-violet-200 bg-violet-50 text-violet-700 dark:border-violet-800 dark:bg-violet-950 dark:text-violet-300'
|
||||
: game.gameType.toLowerCase() === 'vanilla'
|
||||
? 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-300'
|
||||
: 'border-zinc-200 bg-zinc-50 text-zinc-700 dark:border-zinc-800 dark:bg-zinc-700 dark:text-zinc-300'
|
||||
)}
|
||||
>
|
||||
{game.gameType === 'ranked'
|
||||
? 'Ranked Queue'
|
||||
: game.gameType.toLowerCase() === 'vanilla'
|
||||
? 'Vanilla Queue'
|
||||
: 'N/A'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className='text-right font-mono text-slate-500 dark:text-slate-400'>
|
||||
{format.dateTime(game.gameTime, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell className='text-right font-mono text-slate-500 dark:text-slate-400'>
|
||||
{format.dateTime(game.gameTime, {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<GamesTable games={games} />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
Reference in New Issue
Block a user