blanchon's picture
Home: POV tab + DuckDB advanced filter
5a67aa1
import type { ColumnDef, RowData } from '@tanstack/table-core';
import { renderComponent } from '$lib/components/ui/data-table';
import SortHeader from './data-table-sort-header.svelte';
import PlainHeader from './data-table-plain-header.svelte';
import EventCell from './cells/event-cell.svelte';
import MatchIdCell from './cells/match-id-cell.svelte';
import TeamsCell from './cells/teams-cell.svelte';
import ScoreCell from './cells/score-cell.svelte';
import MapCell from './cells/map-cell.svelte';
import MapsListCell from './cells/maps-list-cell.svelte';
import WinnerSideCell from './cells/winner-side-cell.svelte';
import DurationCell from './cells/duration-cell.svelte';
import DateCell from './cells/date-cell.svelte';
import RoundCell from './cells/round-cell.svelte';
import RoundsPlayedCell from './cells/rounds-played-cell.svelte';
import PlayerCell from './cells/player-cell.svelte';
import StartTimeCell from './cells/start-time-cell.svelte';
import type { MapRow, MatchRow, PovRow, RoundRow } from './rows';
import type { Match } from '$lib/types';
declare module '@tanstack/table-core' {
interface ColumnMeta<TData extends RowData, TValue> {
label?: string;
cellClass?: string;
headClass?: string;
}
}
const dateSort = (a: { match_date: string }, b: { match_date: string }) =>
new Date(a.match_date).getTime() - new Date(b.match_date).getTime();
const uploadedSort = (a: { uploaded_at: string }, b: { uploaded_at: string }) =>
new Date(a.uploaded_at).getTime() - new Date(b.uploaded_at).getTime();
const uploadedColumn = <T extends { uploaded_at: string }>(): ColumnDef<T> => ({
id: 'uploaded_at',
accessorFn: (r) => new Date(r.uploaded_at).getTime(),
sortingFn: (a, b) => uploadedSort(a.original, b.original),
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Uploaded' }),
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.uploaded_at }),
enableGlobalFilter: false,
meta: { label: 'Uploaded' }
});
// Cells use `accessorFn` returning the underlying value so global filtering and
// sorting see the right thing; visual rendering happens in `cell` via components.
const matchIdColumn = <T extends { match_id: number }>(): ColumnDef<T> => ({
id: 'match_id',
accessorKey: 'match_id',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Match' }),
cell: ({ row }) => renderComponent(MatchIdCell, { matchId: row.original.match_id }),
meta: { label: 'Match', cellClass: 'w-[6rem] pr-2', headClass: 'w-[6rem] pr-2' }
});
const eventColumn = <T extends { event: string; format?: string }>(): ColumnDef<T> => ({
id: 'event',
accessorKey: 'event',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Event' }),
cell: ({ row }) =>
renderComponent(EventCell, { event: row.original.event, format: row.original.format }),
meta: {
label: 'Event',
cellClass: 'max-w-[16rem]',
headClass: 'max-w-[16rem]'
}
});
export const roundColumns: ColumnDef<RoundRow>[] = [
matchIdColumn<RoundRow>(),
eventColumn<RoundRow>(),
{
id: 'map_name',
accessorKey: 'map_name',
header: () => renderComponent(PlainHeader, { label: 'Map' }),
cell: ({ row }) => renderComponent(MapCell, { map: row.original.map_name }),
enableSorting: false,
filterFn: (row, id, value) => {
if (!value) return true;
return row.getValue<string>(id) === value;
},
meta: { label: 'Map' }
},
{
id: 'teams',
accessorFn: (r) => `${r.team1} ${r.team2}`,
header: () => renderComponent(PlainHeader, { label: 'Teams' }),
cell: ({ row }) =>
renderComponent(TeamsCell, {
team1: row.original.team1,
team2: row.original.team2,
winner: row.original.winner
}),
enableSorting: false,
meta: { label: 'Teams' }
},
{
id: 'round',
accessorKey: 'round',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Round' }),
cell: ({ row }) =>
renderComponent(RoundCell, { round: row.original.round, total: row.original.rounds_played }),
enableGlobalFilter: false,
meta: { label: 'Round' }
},
{
id: 'duration_s',
accessorKey: 'duration_s',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Duration' }),
cell: ({ row }) => renderComponent(DurationCell, { seconds: row.original.duration_s }),
enableGlobalFilter: false,
meta: { label: 'Duration' }
},
{
id: 'match_date',
accessorFn: (r) => new Date(r.match_date).getTime(),
sortingFn: (a, b) => dateSort(a.original, b.original),
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Date' }),
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
enableGlobalFilter: false,
meta: { label: 'Date' }
},
uploadedColumn<RoundRow>()
];
export const mapColumns: ColumnDef<MapRow>[] = [
matchIdColumn<MapRow>(),
eventColumn<MapRow>(),
{
id: 'map_name',
accessorKey: 'map_name',
header: () => renderComponent(PlainHeader, { label: 'Map' }),
cell: ({ row }) => renderComponent(MapCell, { map: row.original.map_name }),
enableSorting: false,
filterFn: (row, id, value) => {
if (!value) return true;
return row.getValue<string>(id) === value;
},
meta: { label: 'Map' }
},
{
id: 'teams',
accessorFn: (r) => `${r.team1} ${r.team2}`,
header: () => renderComponent(PlainHeader, { label: 'Teams' }),
cell: ({ row }) =>
renderComponent(TeamsCell, {
team1: row.original.team1,
team2: row.original.team2,
winner: row.original.winner
}),
enableSorting: false,
meta: { label: 'Teams' }
},
{
id: 'score',
accessorFn: (r) => r.score1 + r.score2,
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Score', align: 'start' }),
cell: ({ row }) =>
renderComponent(ScoreCell, {
score1: row.original.score1,
score2: row.original.score2,
winner: row.original.winner
}),
enableGlobalFilter: false,
meta: { label: 'Score' }
},
{
id: 'winner_side',
accessorKey: 'winner_side',
header: () => renderComponent(PlainHeader, { label: 'Won by' }),
cell: ({ row }) =>
renderComponent(WinnerSideCell, { side: (row.original as Match).winner_side }),
enableSorting: false,
filterFn: (row, id, value) => {
if (!value) return true;
return ((row.getValue<string>(id) ?? '') + '').toLowerCase() === value;
},
meta: { label: 'Won by' }
},
{
id: 'rounds_played',
accessorKey: 'rounds_played',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Rounds' }),
cell: ({ row }) => renderComponent(RoundsPlayedCell, { rounds: row.original.rounds_played }),
enableGlobalFilter: false,
meta: { label: 'Rounds' }
},
{
id: 'duration_s',
accessorKey: 'duration_s',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Duration' }),
cell: ({ row }) => renderComponent(DurationCell, { seconds: row.original.duration_s }),
enableGlobalFilter: false,
meta: { label: 'Duration' }
},
{
id: 'match_date',
accessorFn: (r) => new Date(r.match_date).getTime(),
sortingFn: (a, b) => dateSort(a.original, b.original),
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Date' }),
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
enableGlobalFilter: false,
meta: { label: 'Date' }
},
uploadedColumn<MapRow>()
];
export const povColumns: ColumnDef<PovRow>[] = [
matchIdColumn<PovRow>(),
eventColumn<PovRow>(),
{
id: 'map_name',
accessorKey: 'map_name',
header: () => renderComponent(PlainHeader, { label: 'Map' }),
cell: ({ row }) => renderComponent(MapCell, { map: row.original.map_name }),
enableSorting: false,
filterFn: (row, id, value) => {
if (!value) return true;
return row.getValue<string>(id) === value;
},
meta: { label: 'Map' }
},
{
id: 'round',
accessorKey: 'round',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Round' }),
cell: ({ row }) =>
renderComponent(RoundCell, { round: row.original.round, total: row.original.rounds_played }),
enableGlobalFilter: false,
meta: { label: 'Round' }
},
{
id: 'player',
accessorKey: 'player',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'POV' }),
cell: ({ row }) => renderComponent(PlayerCell, { player: row.original.player }),
enableGlobalFilter: false,
meta: { label: 'POV' }
},
{
id: 'start_t',
accessorKey: 'start_t',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Start' }),
cell: ({ row }) => renderComponent(StartTimeCell, { seconds: row.original.start_t }),
enableGlobalFilter: false,
meta: { label: 'Start' }
},
{
id: 'teams',
accessorFn: (r) => `${r.team1} ${r.team2}`,
header: () => renderComponent(PlainHeader, { label: 'Teams' }),
cell: ({ row }) =>
renderComponent(TeamsCell, {
team1: row.original.team1,
team2: row.original.team2,
winner: row.original.winner
}),
enableSorting: false,
meta: { label: 'Teams' }
},
{
id: 'duration_s',
accessorKey: 'duration_s',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Duration' }),
cell: ({ row }) => renderComponent(DurationCell, { seconds: row.original.duration_s }),
enableGlobalFilter: false,
meta: { label: 'Duration' }
},
{
id: 'match_date',
accessorFn: (r) => new Date(r.match_date).getTime(),
sortingFn: (a, b) => dateSort(a.original, b.original),
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Date' }),
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
enableGlobalFilter: false,
meta: { label: 'Date' }
},
uploadedColumn<PovRow>()
];
export const matchColumns: ColumnDef<MatchRow>[] = [
matchIdColumn<MatchRow>(),
eventColumn<MatchRow>(),
{
id: 'teams',
accessorFn: (r) => `${r.team1} ${r.team2}`,
header: () => renderComponent(PlainHeader, { label: 'Teams' }),
cell: ({ row }) =>
renderComponent(TeamsCell, {
team1: row.original.team1,
team2: row.original.team2,
winner: row.original.winner
}),
enableSorting: false,
meta: { label: 'Teams' }
},
{
id: 'score',
accessorFn: (r) => r.score1 + r.score2,
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Maps' }),
cell: ({ row }) =>
renderComponent(ScoreCell, {
score1: row.original.score1,
score2: row.original.score2,
winner: row.original.winner
}),
enableGlobalFilter: false,
meta: { label: 'Maps won' }
},
{
id: 'maps',
accessorFn: (r) => r.maps.join(' '),
header: () => renderComponent(PlainHeader, { label: 'Map pool' }),
cell: ({ row }) => renderComponent(MapsListCell, { maps: row.original.maps }),
enableSorting: false,
meta: { label: 'Map pool' }
},
{
id: 'rounds_played',
accessorKey: 'rounds_played',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Rounds' }),
cell: ({ row }) => renderComponent(RoundsPlayedCell, { rounds: row.original.rounds_played }),
enableGlobalFilter: false,
meta: { label: 'Rounds' }
},
{
id: 'duration_s',
accessorKey: 'duration_s',
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Duration' }),
cell: ({ row }) => renderComponent(DurationCell, { seconds: row.original.duration_s }),
enableGlobalFilter: false,
meta: { label: 'Duration' }
},
{
id: 'match_date',
accessorFn: (r) => new Date(r.match_date).getTime(),
sortingFn: (a, b) => dateSort(a.original, b.original),
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Date' }),
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
enableGlobalFilter: false,
meta: { label: 'Date' }
},
uploadedColumn<MatchRow>()
];