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;
|
||||
}
|
||||
|
||||
type SortColumn = "pick" | "points";
|
||||
type SortColumn = "pick" | "points" | "sport" | "participant";
|
||||
type SortDirection = "asc" | "desc";
|
||||
|
||||
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" />;
|
||||
}
|
||||
|
||||
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(
|
||||
picks: TeamScoreBreakdownProps["breakdown"]["picks"],
|
||||
sortColumn: SortColumn,
|
||||
sortDirection: SortDirection,
|
||||
) {
|
||||
const dir = sortDirection === "asc" ? 1 : -1;
|
||||
return picks.slice().sort((a, b) => {
|
||||
return picks.toSorted((a, b) => {
|
||||
if (sortColumn === "pick") {
|
||||
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
|
||||
const pointsDiff = a.points - b.points;
|
||||
if (pointsDiff !== 0) return pointsDiff * dir;
|
||||
|
|
@ -142,16 +171,14 @@ export function TeamScoreBreakdown({
|
|||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="min-w-[90px]">
|
||||
<button
|
||||
onClick={() => handleSort("pick")}
|
||||
aria-sort={sortColumn === "pick" ? (sortDirection === "asc" ? "ascending" : "descending") : "none"}
|
||||
className="flex items-center hover:text-foreground cursor-pointer"
|
||||
>
|
||||
Pick #<SortIndicator column="pick" sortColumn={sortColumn} sortDirection={sortDirection} />
|
||||
</button>
|
||||
<SortableHeader column="pick" label="Pick #" sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort} />
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<SortableHeader column="sport" label="Sport" sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort} />
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<SortableHeader column="participant" label="Participant" sortColumn={sortColumn} sortDirection={sortDirection} onSort={handleSort} />
|
||||
</TableHead>
|
||||
<TableHead>Sport</TableHead>
|
||||
<TableHead>Participant</TableHead>
|
||||
<TableHead className="text-center">Position</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<button
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue