Display team owner names instead of team names in draft UI (#24)

* Show manager username instead of team name on draft board

Both the read-only draft board and the active draft room now display
the manager's username (falling back to displayName if no username is
set, and to team name if the team is unassigned) in the column headers
of the draft grid.

https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1

* Address code review: extract ownerMap helper, fix TeamsDraftedGrid

- Extract ownerMap building into app/lib/owner-map.ts to eliminate
  duplication across the two loaders
- Guard against null username AND displayName so the map only contains
  real string values
- Apply username display to TeamsDraftedGrid (the "Teams" tab in the
  active draft room), which was missed in the initial change

https://claude.ai/code/session_01C97GauJaAB83NVWxdpNVh1

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-02-22 16:56:07 -08:00 committed by GitHub
parent b35791e76a
commit ab3437cd73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 61 additions and 4 deletions

View file

@ -25,6 +25,7 @@ interface DraftGridProps {
onForceManualPick?: (pickNumber: number, teamId: string) => void;
autodraftStatus?: Record<string, boolean>;
connectedTeams?: Set<string>;
ownerMap?: Record<string, string>;
}
export function DraftGrid({
@ -39,6 +40,7 @@ export function DraftGrid({
onForceManualPick,
autodraftStatus = {},
connectedTeams = new Set(),
ownerMap = {},
}: DraftGridProps) {
const totalTeams = draftSlots.length;
@ -58,7 +60,7 @@ export function DraftGrid({
<div
className={`font-semibold text-sm truncate px-2 ${!isConnected ? "italic text-muted-foreground" : ""}`}
>
{slot.team.name}
{ownerMap[slot.team.id] || slot.team.name}
</div>
{teamTimers && formatTime && (
<div

View file

@ -42,6 +42,7 @@ interface DraftGridSectionProps {
onForceManualPickOpen: (pickNumber: number, teamId: string) => void;
onReplacePick?: (pickNumber: number, teamId: string) => void;
onRollbackToPick?: (pickNumber: number) => void;
ownerMap?: Record<string, string>;
}
export function DraftGridSection({
@ -57,6 +58,7 @@ export function DraftGridSection({
onForceManualPickOpen,
onReplacePick,
onRollbackToPick,
ownerMap = {},
}: DraftGridSectionProps) {
const currentTeamId = useMemo(
() => draftGrid.flat().find((c) => c.pickNumber === currentPick)?.teamId ?? null,
@ -86,7 +88,7 @@ export function DraftGridSection({
: ""
}`}
>
{slot.team.name}
{ownerMap[slot.team.id] || slot.team.name}
</div>
<div
className={`text-xs font-mono ${getTimerColorClass(teamTime)}`}

View file

@ -9,6 +9,7 @@ interface TeamsDraftedGridProps {
name: string;
};
}>;
ownerMap?: Record<string, string>;
picks: Array<{
id: string;
team: {
@ -38,6 +39,7 @@ export function TeamsDraftedGrid({
picks,
sports,
season,
ownerMap = {},
}: TeamsDraftedGridProps) {
// Calculate picks by team and sport
const picksByTeamAndSport = useMemo(() => {
@ -97,7 +99,7 @@ export function TeamsDraftedGrid({
>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">
{slot.team.name}
{ownerMap[slot.team.id] || slot.team.name}
</div>
<div className="text-xs text-muted-foreground font-normal">
{flexUsed} of {season.numFlexPicks} flex

39
app/lib/owner-map.ts Normal file
View file

@ -0,0 +1,39 @@
import { findUserByClerkId } from "~/models/user";
/**
* Builds a map of teamId -> manager username (or displayName fallback).
* Accepts any array of objects that have a `team` with `id` and `ownerId`.
*/
export async function buildOwnerMap(
slots: Array<{ team: { id: string; ownerId: string | null } }>
): Promise<Record<string, string>> {
const ownerIds = slots
.map((s) => s.team.ownerId)
.filter((id): id is string => id !== null);
const uniqueOwnerIds = [...new Set(ownerIds)];
const ownerEntries = await Promise.all(
uniqueOwnerIds.map(async (ownerId) => {
const user = await findUserByClerkId(ownerId);
const name = user?.username || user?.displayName || null;
return name ? { ownerId, name } : null;
})
);
const ownerByClerkId = new Map(
ownerEntries
.filter((e): e is NonNullable<typeof e> => e !== null)
.map((e) => [e.ownerId, e.name])
);
const ownerMap: Record<string, string> = {};
for (const slot of slots) {
const name = slot.team.ownerId
? ownerByClerkId.get(slot.team.ownerId)
: undefined;
if (name) {
ownerMap[slot.team.id] = name;
}
}
return ownerMap;
}

View file

@ -6,6 +6,7 @@ import * as schema from "~/database/schema";
import { DraftGrid } from "~/components/DraftGrid";
import { useDraftSocket } from "~/hooks/useDraftSocket";
import { useState, useEffect } from "react";
import { buildOwnerMap } from "~/lib/owner-map";
export async function loader(args: any) {
const { params } = args;
@ -102,15 +103,18 @@ export async function loader(args: any) {
.where(eq(schema.draftPicks.seasonId, seasonId))
.orderBy(asc(schema.draftPicks.pickNumber));
const ownerMap = await buildOwnerMap(draftSlots);
return {
season,
draftSlots,
draftPicks,
ownerMap,
};
}
export default function DraftBoard() {
const { season, draftSlots, draftPicks: initialPicks } = useLoaderData<typeof loader>();
const { season, draftSlots, draftPicks: initialPicks, ownerMap } = useLoaderData<typeof loader>();
const { isConnected, on, off } = useDraftSocket(season.id);
const [picks, setPicks] = useState(initialPicks);
const [currentPick, setCurrentPick] = useState(season.currentPickNumber || 1);
@ -186,6 +190,7 @@ export default function DraftBoard() {
draftSlots={draftSlots}
draftGrid={draftGrid}
currentPick={currentPick}
ownerMap={ownerMap}
/>
</div>
</div>

View file

@ -11,6 +11,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "~/components/ui/dialog";
import { getAuth } from "@clerk/react-router/server";
import { getTeamQueue } from "~/models/draft-queue";
import { buildOwnerMap } from "~/lib/owner-map";
import { DraftSidebar } from "~/components/DraftSidebar";
import { TeamsDraftedGrid } from "~/components/draft/TeamsDraftedGrid";
import { QueueSection } from "~/components/draft/QueueSection";
@ -166,6 +167,8 @@ export async function loader(args: any) {
? (autodraftSettings.find((s) => s.teamId === userTeam.id) ?? null)
: null;
const ownerMap = await buildOwnerMap(draftSlots);
return {
season,
draftSlots,
@ -179,6 +182,7 @@ export async function loader(args: any) {
isCommissioner: !!isCommissioner,
currentUserId: userId,
numFlexPicks: Math.max(0, season.draftRounds - seasonSports.length),
ownerMap,
};
}
@ -196,6 +200,7 @@ export default function DraftRoom() {
isCommissioner,
currentUserId,
numFlexPicks,
ownerMap,
} = useLoaderData<typeof loader>();
const { revalidate } = useRevalidator();
const { isConnected, connectionError, isReconnecting, on, off } = useDraftSocket(season.id, userTeam?.id);
@ -1224,6 +1229,7 @@ export default function DraftRoom() {
autodraftStatus={autodraftStatus}
connectedTeams={connectedTeams}
isCommissioner={isCommissioner}
ownerMap={ownerMap}
onAdjustTimeBankOpen={isCommissioner ? handleAdjustTimeBankOpen : undefined}
onForceAutopick={handleForceAutopick}
onForceManualPickOpen={(pickNumber, teamId) => {
@ -1244,6 +1250,7 @@ export default function DraftRoom() {
picks={picks}
sports={seasonSportsData}
season={{ numFlexPicks }}
ownerMap={ownerMap}
/>
</div>
</TabsContent>