Add sortable Sport and Participant columns to team breakdown table (#208)
- Sort by sport (secondary: participant) or participant name - Extract SortableHeader component to reduce header button repetition - Use toSorted() instead of slice().sort() (oxlint fix) - Use localeCompare with sensitivity: 'base' for case-insensitive string sorts Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
194f9087a3
commit
b672c50d78
1 changed files with 38 additions and 11 deletions
|
|
@ -39,7 +39,7 @@ interface TeamScoreBreakdownProps {
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortColumn = "pick" | "points";
|
type SortColumn = "pick" | "points" | "sport" | "participant";
|
||||||
type SortDirection = "asc" | "desc";
|
type SortDirection = "asc" | "desc";
|
||||||
|
|
||||||
function SortIndicator({ column, sortColumn, sortDirection }: { column: SortColumn; sortColumn: SortColumn; sortDirection: SortDirection }) {
|
function SortIndicator({ column, sortColumn, sortDirection }: { column: SortColumn; sortColumn: SortColumn; sortDirection: SortDirection }) {
|
||||||
|
|
@ -49,16 +49,45 @@ function SortIndicator({ column, sortColumn, sortDirection }: { column: SortColu
|
||||||
: <ArrowDown className="ml-1 h-3 w-3" />;
|
: <ArrowDown className="ml-1 h-3 w-3" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SortableHeader({ column, label, sortColumn, sortDirection, onSort, className }: {
|
||||||
|
column: SortColumn;
|
||||||
|
label: string;
|
||||||
|
sortColumn: SortColumn;
|
||||||
|
sortDirection: SortDirection;
|
||||||
|
onSort: (column: SortColumn) => void;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => onSort(column)}
|
||||||
|
aria-sort={sortColumn === column ? (sortDirection === "asc" ? "ascending" : "descending") : "none"}
|
||||||
|
className={`flex items-center hover:text-foreground cursor-pointer ${className ?? ""}`}
|
||||||
|
>
|
||||||
|
{label}<SortIndicator column={column} sortColumn={sortColumn} sortDirection={sortDirection} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmp = (a: string, b: string) => a.localeCompare(b, undefined, { sensitivity: "base" });
|
||||||
|
|
||||||
function sortPicks(
|
function sortPicks(
|
||||||
picks: TeamScoreBreakdownProps["breakdown"]["picks"],
|
picks: TeamScoreBreakdownProps["breakdown"]["picks"],
|
||||||
sortColumn: SortColumn,
|
sortColumn: SortColumn,
|
||||||
sortDirection: SortDirection,
|
sortDirection: SortDirection,
|
||||||
) {
|
) {
|
||||||
const dir = sortDirection === "asc" ? 1 : -1;
|
const dir = sortDirection === "asc" ? 1 : -1;
|
||||||
return picks.slice().sort((a, b) => {
|
return picks.toSorted((a, b) => {
|
||||||
if (sortColumn === "pick") {
|
if (sortColumn === "pick") {
|
||||||
return (a.pickNumber - b.pickNumber) * dir;
|
return (a.pickNumber - b.pickNumber) * dir;
|
||||||
}
|
}
|
||||||
|
if (sortColumn === "sport") {
|
||||||
|
const sportCmp = cmp(a.participant.sport, b.participant.sport) * dir;
|
||||||
|
if (sportCmp !== 0) return sportCmp;
|
||||||
|
return cmp(a.participant.name, b.participant.name) * dir;
|
||||||
|
}
|
||||||
|
if (sortColumn === "participant") {
|
||||||
|
return cmp(a.participant.name, b.participant.name) * dir;
|
||||||
|
}
|
||||||
// points: sort by actual first, then projected
|
// points: sort by actual first, then projected
|
||||||
const pointsDiff = a.points - b.points;
|
const pointsDiff = a.points - b.points;
|
||||||
if (pointsDiff !== 0) return pointsDiff * dir;
|
if (pointsDiff !== 0) return pointsDiff * dir;
|
||||||
|
|
@ -142,16 +171,14 @@ export function TeamScoreBreakdown({
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="min-w-[90px]">
|
<TableHead className="min-w-[90px]">
|
||||||
<button
|
<SortableHeader column="pick" label="Pick #" sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort} />
|
||||||
onClick={() => handleSort("pick")}
|
</TableHead>
|
||||||
aria-sort={sortColumn === "pick" ? (sortDirection === "asc" ? "ascending" : "descending") : "none"}
|
<TableHead>
|
||||||
className="flex items-center hover:text-foreground cursor-pointer"
|
<SortableHeader column="sport" label="Sport" sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort} />
|
||||||
>
|
</TableHead>
|
||||||
Pick #<SortIndicator column="pick" sortColumn={sortColumn} sortDirection={sortDirection} />
|
<TableHead>
|
||||||
</button>
|
<SortableHeader column="participant" label="Participant" sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort} />
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead>Sport</TableHead>
|
|
||||||
<TableHead>Participant</TableHead>
|
|
||||||
<TableHead className="text-center">Position</TableHead>
|
<TableHead className="text-center">Position</TableHead>
|
||||||
<TableHead className="text-right">
|
<TableHead className="text-right">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue