Show team name + username in standings, extract TeamNameDisplay component

- Add TeamNameDisplay component that renders team name (as link) with owner username below, matching the league homepage style
- Update StandingsTable to use TeamNameDisplay with owner username shown below team name
- Update league homepage standings section to use TeamNameDisplay
- Add ownerName/teamOwnerId fields to TeamStanding type
- Extend getSeasonStandings to include teamOwnerId from team relation
- Fetch and attach owner display names in the full standings page loader

https://claude.ai/code/session_01EYgGnuTBaRVdBDapJRTxDZ
This commit is contained in:
Claude 2026-03-20 03:11:34 +00:00
parent fa0e798db7
commit 5fd9c6410b
No known key found for this signature in database
6 changed files with 67 additions and 23 deletions

View file

@ -1,6 +1,6 @@
import { Link } from "react-router";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "~/components/ui/table"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "~/components/ui/table";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { TeamNameDisplay } from "~/components/ui/team-name-display";
import { type TeamStanding } from "~/types/standings"; import { type TeamStanding } from "~/types/standings";
interface StandingsTableProps { interface StandingsTableProps {
@ -54,13 +54,12 @@ export function StandingsTable({
)} )}
</div> </div>
</TableCell> </TableCell>
<TableCell className="font-medium"> <TableCell>
<Link <TeamNameDisplay
to={`/leagues/${leagueId}/standings/${seasonId}/teams/${standing.teamId}`} teamName={standing.teamName}
className="hover:underline hover:text-primary transition-colors" ownerName={standing.ownerName}
> href={`/leagues/${leagueId}/standings/${seasonId}/teams/${standing.teamId}`}
{standing.teamName} />
</Link>
</TableCell> </TableCell>
<TableCell className="text-right font-semibold"> <TableCell className="text-right font-semibold">
{standing.actualPoints !== null && standing.actualPoints !== undefined {standing.actualPoints !== null && standing.actualPoints !== undefined

View file

@ -0,0 +1,31 @@
import { Link } from "react-router";
interface TeamNameDisplayProps {
teamName: string;
ownerName?: string | null;
href?: string;
}
/**
* Displays a team name with optional owner username below it.
* If href is provided, the team name is rendered as a link.
*/
export function TeamNameDisplay({ teamName, ownerName, href }: TeamNameDisplayProps) {
return (
<div className="min-w-0">
{href ? (
<Link
to={href}
className="font-medium text-sm hover:underline truncate block"
>
{teamName}
</Link>
) : (
<p className="font-medium text-sm truncate">{teamName}</p>
)}
{ownerName && (
<p className="text-xs text-muted-foreground truncate">{ownerName}</p>
)}
</div>
);
}

View file

@ -29,6 +29,7 @@ export async function getSeasonStandings(
return sorted.map((standing) => ({ return sorted.map((standing) => ({
teamId: standing.teamId, teamId: standing.teamId,
teamName: standing.team.name, teamName: standing.team.name,
teamOwnerId: standing.team.ownerId,
totalPoints: parseFloat(standing.totalPoints), totalPoints: parseFloat(standing.totalPoints),
currentRank: standing.currentRank, currentRank: standing.currentRank,
previousRank: standing.previousRank, previousRank: standing.previousRank,

View file

@ -9,6 +9,7 @@ import { StandingsTable } from "~/components/standings/StandingsTable";
import { getSevenDayStandingsChange, getSeasonPointProgression } from "~/models/standings"; import { getSevenDayStandingsChange, getSeasonPointProgression } from "~/models/standings";
import { isSeasonComplete, getSeasonCompletionPercentage } from "~/lib/season-helpers.server"; import { isSeasonComplete, getSeasonCompletionPercentage } from "~/lib/season-helpers.server";
import { PointProgressionChart } from "~/components/standings/PointProgressionChart"; import { PointProgressionChart } from "~/components/standings/PointProgressionChart";
import { findUsersByClerkIds, getUserDisplayName } from "~/models/user";
import type { Route } from "./+types/$leagueId.standings.$seasonId"; import type { Route } from "./+types/$leagueId.standings.$seasonId";
export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors {
@ -73,11 +74,26 @@ export async function loader(args: Route.LoaderArgs) {
// Fetch standings with 7-day comparison // Fetch standings with 7-day comparison
const standingsWithComparison = await getSevenDayStandingsChange(seasonId, db); const standingsWithComparison = await getSevenDayStandingsChange(seasonId, db);
// Fetch owner display names for all teams in standings
const ownerIds = [
...new Set(
standingsWithComparison
.map((s) => s.teamOwnerId)
.filter((id): id is string => id != null)
),
];
const userRows = await findUsersByClerkIds(ownerIds);
const userByClerkId = new Map(userRows.map((u) => [u.clerkId, u]));
// Map to format expected by component (use sevenDayRankChange instead of previousRank) // Map to format expected by component (use sevenDayRankChange instead of previousRank)
const formattedStandings = standingsWithComparison.map((standing) => ({ const formattedStandings = standingsWithComparison.map((standing) => {
const user = standing.teamOwnerId ? userByClerkId.get(standing.teamOwnerId) : undefined;
return {
...standing, ...standing,
rankChange: standing.sevenDayRankChange, rankChange: standing.sevenDayRankChange,
})); ownerName: user ? getUserDisplayName(user) : null,
};
});
// Fetch historical progression data // Fetch historical progression data
const progressionData = await getSeasonPointProgression(seasonId, db); const progressionData = await getSeasonPointProgression(seasonId, db);

View file

@ -14,6 +14,7 @@ import {
} from "~/components/ui/card"; } from "~/components/ui/card";
import { SportSeasonCard } from "~/components/sports/SportSeasonCard"; import { SportSeasonCard } from "~/components/sports/SportSeasonCard";
import { UpcomingCalendarPanel } from "~/components/sport-season/UpcomingCalendarPanel"; import { UpcomingCalendarPanel } from "~/components/sport-season/UpcomingCalendarPanel";
import { TeamNameDisplay } from "~/components/ui/team-name-display";
import { getDisplayRank } from "~/lib/standings-display"; import { getDisplayRank } from "~/lib/standings-display";
export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors {
@ -196,17 +197,11 @@ export default function LeagueHome({ loaderData }: Route.ComponentProps) {
{getDisplayRank(standing, standings.length)} {getDisplayRank(standing, standings.length)}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<Link <TeamNameDisplay
to={`/leagues/${league.id}/standings/${season.id}/teams/${team.id}`} teamName={team.name}
className="font-medium text-sm hover:underline truncate block" ownerName={ownerName}
> href={`/leagues/${league.id}/standings/${season.id}/teams/${team.id}`}
{team.name} />
</Link>
{ownerName && (
<p className="text-xs text-muted-foreground truncate">
{ownerName}
</p>
)}
</div> </div>
<div className="text-sm font-medium tabular-nums"> <div className="text-sm font-medium tabular-nums">
{standing ? standing.totalPoints : 0} pts {standing ? standing.totalPoints : 0} pts

View file

@ -5,6 +5,8 @@
export interface TeamStanding { export interface TeamStanding {
teamId: string; teamId: string;
teamName: string; teamName: string;
teamOwnerId?: string | null;
ownerName?: string | null;
totalPoints: number; totalPoints: number;
currentRank: number; currentRank: number;
previousRank: number | null; previousRank: number | null;