Spaces:
Running
Running
Match table: add Uploaded column, sort by it by default
Browse filesSurface the per-row uploaded_at timestamp so freshly ingested matches
float to the top by default. The match-row roll-up takes the latest
upload across the series' maps; rounds use their own per-round
uploaded_at; the legacy Date (match_date) column stays so users can
still browse by when matches were originally played.
src/lib/components/match-table/columns.ts
CHANGED
|
@@ -27,6 +27,19 @@ declare module '@tanstack/table-core' {
|
|
| 27 |
const dateSort = (a: { match_date: string }, b: { match_date: string }) =>
|
| 28 |
new Date(a.match_date).getTime() - new Date(b.match_date).getTime();
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
// Cells use `accessorFn` returning the underlying value so global filtering and
|
| 31 |
// sorting see the right thing; visual rendering happens in `cell` via components.
|
| 32 |
|
|
@@ -104,7 +117,8 @@ export const roundColumns: ColumnDef<RoundRow>[] = [
|
|
| 104 |
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
|
| 105 |
enableGlobalFilter: false,
|
| 106 |
meta: { label: 'Date' }
|
| 107 |
-
}
|
|
|
|
| 108 |
];
|
| 109 |
|
| 110 |
export const mapColumns: ColumnDef<MapRow>[] = [
|
|
@@ -185,7 +199,8 @@ export const mapColumns: ColumnDef<MapRow>[] = [
|
|
| 185 |
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
|
| 186 |
enableGlobalFilter: false,
|
| 187 |
meta: { label: 'Date' }
|
| 188 |
-
}
|
|
|
|
| 189 |
];
|
| 190 |
|
| 191 |
export const matchColumns: ColumnDef<MatchRow>[] = [
|
|
@@ -249,5 +264,6 @@ export const matchColumns: ColumnDef<MatchRow>[] = [
|
|
| 249 |
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
|
| 250 |
enableGlobalFilter: false,
|
| 251 |
meta: { label: 'Date' }
|
| 252 |
-
}
|
|
|
|
| 253 |
];
|
|
|
|
| 27 |
const dateSort = (a: { match_date: string }, b: { match_date: string }) =>
|
| 28 |
new Date(a.match_date).getTime() - new Date(b.match_date).getTime();
|
| 29 |
|
| 30 |
+
const uploadedSort = (a: { uploaded_at: string }, b: { uploaded_at: string }) =>
|
| 31 |
+
new Date(a.uploaded_at).getTime() - new Date(b.uploaded_at).getTime();
|
| 32 |
+
|
| 33 |
+
const uploadedColumn = <T extends { uploaded_at: string }>(): ColumnDef<T> => ({
|
| 34 |
+
id: 'uploaded_at',
|
| 35 |
+
accessorFn: (r) => new Date(r.uploaded_at).getTime(),
|
| 36 |
+
sortingFn: (a, b) => uploadedSort(a.original, b.original),
|
| 37 |
+
header: ({ column }) => renderComponent(SortHeader, { column, label: 'Uploaded' }),
|
| 38 |
+
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.uploaded_at }),
|
| 39 |
+
enableGlobalFilter: false,
|
| 40 |
+
meta: { label: 'Uploaded' }
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
// Cells use `accessorFn` returning the underlying value so global filtering and
|
| 44 |
// sorting see the right thing; visual rendering happens in `cell` via components.
|
| 45 |
|
|
|
|
| 117 |
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
|
| 118 |
enableGlobalFilter: false,
|
| 119 |
meta: { label: 'Date' }
|
| 120 |
+
},
|
| 121 |
+
uploadedColumn<RoundRow>()
|
| 122 |
];
|
| 123 |
|
| 124 |
export const mapColumns: ColumnDef<MapRow>[] = [
|
|
|
|
| 199 |
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
|
| 200 |
enableGlobalFilter: false,
|
| 201 |
meta: { label: 'Date' }
|
| 202 |
+
},
|
| 203 |
+
uploadedColumn<MapRow>()
|
| 204 |
];
|
| 205 |
|
| 206 |
export const matchColumns: ColumnDef<MatchRow>[] = [
|
|
|
|
| 264 |
cell: ({ row }) => renderComponent(DateCell, { iso: row.original.match_date }),
|
| 265 |
enableGlobalFilter: false,
|
| 266 |
meta: { label: 'Date' }
|
| 267 |
+
},
|
| 268 |
+
uploadedColumn<MatchRow>()
|
| 269 |
];
|
src/lib/components/match-table/rows.ts
CHANGED
|
@@ -20,6 +20,7 @@ export type RoundRow = {
|
|
| 20 |
winner: Match['winner'];
|
| 21 |
format: string;
|
| 22 |
match_date: string;
|
|
|
|
| 23 |
rounds_played: number;
|
| 24 |
};
|
| 25 |
|
|
@@ -33,6 +34,7 @@ export type MatchRow = {
|
|
| 33 |
winner: Match['winner'] | '';
|
| 34 |
format: string;
|
| 35 |
match_date: string;
|
|
|
|
| 36 |
maps: string[];
|
| 37 |
maps_played: number;
|
| 38 |
first_map: string;
|
|
@@ -74,6 +76,7 @@ export function buildRoundRows(matches: Match[], rounds: Round[]): RoundRow[] {
|
|
| 74 |
winner: m.winner,
|
| 75 |
format: m.format,
|
| 76 |
match_date: m.match_date,
|
|
|
|
| 77 |
rounds_played: m.rounds_played
|
| 78 |
});
|
| 79 |
}
|
|
@@ -105,6 +108,12 @@ export function buildMatchRows(matches: Match[], rounds: Round[]): MatchRow[] {
|
|
| 105 |
(acc, m) => acc + (durationByMap.get(`${m.match_id}|${m.map_name}`) ?? 0),
|
| 106 |
0
|
| 107 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
rows.push({
|
| 109 |
match_id: matchId,
|
| 110 |
event: head.event,
|
|
@@ -115,6 +124,7 @@ export function buildMatchRows(matches: Match[], rounds: Round[]): MatchRow[] {
|
|
| 115 |
winner: team1MapWins > team2MapWins ? 'team1' : team2MapWins > team1MapWins ? 'team2' : '',
|
| 116 |
format: head.format,
|
| 117 |
match_date: head.match_date,
|
|
|
|
| 118 |
maps: sorted.map((m) => m.map_name),
|
| 119 |
maps_played: sorted.length,
|
| 120 |
first_map: head.map_name,
|
|
@@ -124,7 +134,8 @@ export function buildMatchRows(matches: Match[], rounds: Round[]): MatchRow[] {
|
|
| 124 |
}
|
| 125 |
rows.sort(
|
| 126 |
(a, b) =>
|
| 127 |
-
new Date(b.
|
|
|
|
| 128 |
);
|
| 129 |
return rows;
|
| 130 |
}
|
|
|
|
| 20 |
winner: Match['winner'];
|
| 21 |
format: string;
|
| 22 |
match_date: string;
|
| 23 |
+
uploaded_at: string;
|
| 24 |
rounds_played: number;
|
| 25 |
};
|
| 26 |
|
|
|
|
| 34 |
winner: Match['winner'] | '';
|
| 35 |
format: string;
|
| 36 |
match_date: string;
|
| 37 |
+
uploaded_at: string;
|
| 38 |
maps: string[];
|
| 39 |
maps_played: number;
|
| 40 |
first_map: string;
|
|
|
|
| 76 |
winner: m.winner,
|
| 77 |
format: m.format,
|
| 78 |
match_date: m.match_date,
|
| 79 |
+
uploaded_at: r.uploaded_at,
|
| 80 |
rounds_played: m.rounds_played
|
| 81 |
});
|
| 82 |
}
|
|
|
|
| 108 |
(acc, m) => acc + (durationByMap.get(`${m.match_id}|${m.map_name}`) ?? 0),
|
| 109 |
0
|
| 110 |
);
|
| 111 |
+
// Use the latest upload across the match's maps so the row reflects when
|
| 112 |
+
// the whole series finished landing in the dataset.
|
| 113 |
+
const latestUpload = sorted.reduce(
|
| 114 |
+
(acc, m) => (m.uploaded_at > acc ? m.uploaded_at : acc),
|
| 115 |
+
head.uploaded_at
|
| 116 |
+
);
|
| 117 |
rows.push({
|
| 118 |
match_id: matchId,
|
| 119 |
event: head.event,
|
|
|
|
| 124 |
winner: team1MapWins > team2MapWins ? 'team1' : team2MapWins > team1MapWins ? 'team2' : '',
|
| 125 |
format: head.format,
|
| 126 |
match_date: head.match_date,
|
| 127 |
+
uploaded_at: latestUpload,
|
| 128 |
maps: sorted.map((m) => m.map_name),
|
| 129 |
maps_played: sorted.length,
|
| 130 |
first_map: head.map_name,
|
|
|
|
| 134 |
}
|
| 135 |
rows.sort(
|
| 136 |
(a, b) =>
|
| 137 |
+
new Date(b.uploaded_at).getTime() - new Date(a.uploaded_at).getTime() ||
|
| 138 |
+
b.match_id - a.match_id
|
| 139 |
);
|
| 140 |
return rows;
|
| 141 |
}
|
src/routes/+page.svelte
CHANGED
|
@@ -85,7 +85,7 @@
|
|
| 85 |
|
| 86 |
const newTableState = (): TableState => ({
|
| 87 |
pagination: { pageIndex: 0, pageSize: 25 },
|
| 88 |
-
sorting: [{ id: '
|
| 89 |
columnFilters: [],
|
| 90 |
columnVisibility: {},
|
| 91 |
globalFilter: ''
|
|
|
|
| 85 |
|
| 86 |
const newTableState = (): TableState => ({
|
| 87 |
pagination: { pageIndex: 0, pageSize: 25 },
|
| 88 |
+
sorting: [{ id: 'uploaded_at', desc: true }],
|
| 89 |
columnFilters: [],
|
| 90 |
columnVisibility: {},
|
| 91 |
globalFilter: ''
|