Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 83 additions & 68 deletions app/tipping/group-admin/_components/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ import {
TableRow,
} from '@/components/ui/table'
import { RACE_PREDICTION_FIELDS } from '@/constants'
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from '@/components/ui/empty'
import { FilterX } from 'lucide-react'

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
Expand Down Expand Up @@ -67,74 +76,80 @@ export function DataTable<TData, TValue>({

return (
<div>
<div className='overflow-hidden rounded-md border'>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className='h-24 text-center'
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className='flex items-center justify-end space-x-2 py-4'>
<Button
variant='outline'
size='sm'
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant='outline'
size='sm'
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
{table.getRowModel().rows?.length ? (
<>
<div className='overflow-hidden rounded-md border'>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className='flex items-center justify-end space-x-2 py-4'>
<Button
variant='outline'
size='sm'
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant='outline'
size='sm'
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</>
) : (
<Empty className='border'>
<EmptyHeader>
<EmptyMedia variant='icon'>
<FilterX />
</EmptyMedia>
<EmptyTitle>No predictions found</EmptyTitle>
<EmptyDescription>
No predictions match the selected filters. Try adjusting your
filters or create a new prediction.
</EmptyDescription>
</EmptyHeader>
</Empty>
)}
</div>
)
function sortByPosition<TData>(): SortingFn<TData> {
Expand Down
90 changes: 90 additions & 0 deletions app/tipping/group-admin/_components/filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client'

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Database } from '@/db/types'

interface FiltersProps {
races: Array<Pick<Database.Race, 'id' | 'locality' | 'grandPrixDate'>>
users: Array<{ id: string; name: string }>
selectedRaceId: string | null
selectedUserId: string | null
onRaceChange: (raceId: string | null) => void
onUserChange: (userId: string | null) => void
}

export function Filters({
races,
users,
selectedRaceId,
selectedUserId,
onRaceChange,
onUserChange,
}: FiltersProps) {
// Sort races by date (most recent first)
const sortedRaces = [...races].sort(
(a, b) =>
new Date(b.grandPrixDate).getTime() -
new Date(a.grandPrixDate).getTime(),
)

// Sort users alphabetically
const sortedUsers = [...users].sort((a, b) => a.name.localeCompare(b.name))

return (
<div className='flex gap-4 flex-wrap'>
<div className='flex flex-col gap-1.5'>
<label htmlFor='race-filter' className='text-sm font-medium'>
Race
</label>
<Select
value={selectedRaceId ?? 'all'}
onValueChange={(value) =>
onRaceChange(value === 'all' ? null : value)
}
>
<SelectTrigger id='race-filter' className='w-[200px]'>
<SelectValue placeholder='All races' />
</SelectTrigger>
<SelectContent>
<SelectItem value='all'>All races</SelectItem>
{sortedRaces.map((race) => (
<SelectItem key={race.id} value={race.id}>
{race.locality}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

<div className='flex flex-col gap-1.5'>
<label htmlFor='user-filter' className='text-sm font-medium'>
User
</label>
<Select
value={selectedUserId ?? 'all'}
onValueChange={(value) =>
onUserChange(value === 'all' ? null : value)
}
>
<SelectTrigger id='user-filter' className='w-[200px]'>
<SelectValue placeholder='All users' />
</SelectTrigger>
<SelectContent>
<SelectItem value='all'>All users</SelectItem>
{sortedUsers.map((user) => (
<SelectItem key={user.id} value={user.id}>
{user.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
)
}
45 changes: 45 additions & 0 deletions app/tipping/group-admin/_components/predictions-table-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client'

import { useState, useMemo } from 'react'
import { DataTable } from './data-table'
import { columns } from './columns'
import { Filters } from './filters'
import { PredictionRow } from '../_utils/rows'
import { Database } from '@/db/types'

interface PredictionsTableWrapperProps {
rows: PredictionRow[]
races: Array<Pick<Database.Race, 'id' | 'locality' | 'grandPrixDate'>>
users: Array<{ id: string; name: string }>
}

export function PredictionsTableWrapper({
rows,
races,
users,
}: PredictionsTableWrapperProps) {
const [selectedRaceId, setSelectedRaceId] = useState<string | null>(null)
const [selectedUserId, setSelectedUserId] = useState<string | null>(null)

const filteredRows = useMemo(() => {
return rows.filter((row) => {
const matchesRace = selectedRaceId ? row.race.id === selectedRaceId : true
const matchesUser = selectedUserId ? row.user.id === selectedUserId : true
return matchesRace && matchesUser
})
}, [rows, selectedRaceId, selectedUserId])

return (
<div className='space-y-4'>
<Filters
races={races}
users={users}
selectedRaceId={selectedRaceId}
selectedUserId={selectedUserId}
onRaceChange={setSelectedRaceId}
onUserChange={setSelectedUserId}
/>
<DataTable columns={columns} data={filteredRows} />
</div>
)
}
5 changes: 2 additions & 3 deletions app/tipping/group-admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { columns } from './_components/columns'
import { DataTable } from './_components/data-table'
import EmptyGroup from '@/components/empty-group'
import { verifyIsAdmin, verifySession } from '@/lib/dal'
import { PredictionsTableWrapper } from './_components/predictions-table-wrapper'
import {
getConstructorOptions,
getCurrentGroup,
Expand Down Expand Up @@ -87,7 +86,7 @@ export default async function GroupSettings() {
<CreateOrEditTipDialog {...formProps} />
</div>
<TipFormProvider context={formProps}>
<DataTable columns={columns} data={rows} />
<PredictionsTableWrapper rows={rows} races={races} users={members} />
</TipFormProvider>
</section>
</div>
Expand Down
Loading