diff --git a/app/models/__tests__/sports-season.test.ts b/app/models/__tests__/sports-season.test.ts index 4bbabd1..34c71ff 100644 --- a/app/models/__tests__/sports-season.test.ts +++ b/app/models/__tests__/sports-season.test.ts @@ -36,9 +36,9 @@ const pastStart = "2020-01-01"; const pastEnd = "2020-12-31"; const mockSeasons = [ - { id: "season-1", name: "2025 NBA Playoffs", draftOn: today, draftOff: future, year: 2025 }, - { id: "season-2", name: "2025 F1 Season", draftOn: today, draftOff: future, year: 2025 }, - { id: "season-3", name: "2024 Old Golf", draftOn: pastStart, draftOff: pastEnd, year: 2024 }, + { id: "season-1", name: "2025 NBA Playoffs", draftOn: today, draftOff: future, year: 2025, status: "upcoming", sport: { name: "NBA" } }, + { id: "season-2", name: "2025 F1 Season", draftOn: today, draftOff: future, year: 2025, status: "upcoming", sport: { name: "F1" } }, + { id: "season-3", name: "2024 Old Golf", draftOn: pastStart, draftOff: pastEnd, year: 2024, status: "completed", sport: { name: "Golf" } }, ]; function makeMockDb(seasons: typeof mockSeasons) { @@ -61,6 +61,39 @@ describe("findAllSportsSeasons", () => { const result = await findAllSportsSeasons(); expect(result).toHaveLength(3); }); + + it("sorts active before upcoming before completed", async () => { + const seasons = [ + { id: "s1", name: "Completed", draftOn: pastStart, draftOff: pastEnd, year: 2024, status: "completed", sport: { name: "Sport" } }, + { id: "s2", name: "Active", draftOn: today, draftOff: future, year: 2025, status: "active", sport: { name: "Sport" } }, + { id: "s3", name: "Upcoming", draftOn: today, draftOff: future, year: 2026, status: "upcoming", sport: { name: "Sport" } }, + ]; + vi.mocked(database).mockReturnValue(makeMockDb(seasons) as never); + const result = await findAllSportsSeasons(); + expect(result.map((s) => s.status)).toEqual(["active", "upcoming", "completed"]); + }); + + it("sorts by sport name alphabetically within the same status", async () => { + const seasons = [ + { id: "s1", name: "Zebra Season", draftOn: today, draftOff: future, year: 2025, status: "active", sport: { name: "Zebra" } }, + { id: "s2", name: "Apple Season", draftOn: today, draftOff: future, year: 2025, status: "active", sport: { name: "Apple" } }, + { id: "s3", name: "Mango Season", draftOn: today, draftOff: future, year: 2025, status: "active", sport: { name: "Mango" } }, + ]; + vi.mocked(database).mockReturnValue(makeMockDb(seasons) as never); + const result = await findAllSportsSeasons(); + expect(result.map((s) => s.sport.name)).toEqual(["Apple", "Mango", "Zebra"]); + }); + + it("sorts by year ascending within the same status and sport name", async () => { + const seasons = [ + { id: "s1", name: "NFL 2026", draftOn: today, draftOff: future, year: 2026, status: "upcoming", sport: { name: "NFL" } }, + { id: "s2", name: "NFL 2024", draftOn: today, draftOff: future, year: 2024, status: "upcoming", sport: { name: "NFL" } }, + { id: "s3", name: "NFL 2025", draftOn: today, draftOff: future, year: 2025, status: "upcoming", sport: { name: "NFL" } }, + ]; + vi.mocked(database).mockReturnValue(makeMockDb(seasons) as never); + const result = await findAllSportsSeasons(); + expect(result.map((s) => s.year)).toEqual([2024, 2025, 2026]); + }); }); describe("findDraftableSportsSeasons", () => { diff --git a/app/models/sports-season.ts b/app/models/sports-season.ts index c3633e1..6a5a9c8 100644 --- a/app/models/sports-season.ts +++ b/app/models/sports-season.ts @@ -15,6 +15,9 @@ export type NewSportsSeason = typeof schema.sportsSeasons.$inferInsert; export type SportsSeasonWithSport = SportsSeason & { sport: typeof schema.sports.$inferSelect; }; +export type SportsSeasonListItem = SportsSeasonWithSport & { + participants: Array<{ id: string }>; +}; export type SportsSeasonStatus = "upcoming" | "active" | "completed"; export type ScoringType = "playoffs" | "regular_season" | "majors"; @@ -85,10 +88,9 @@ export async function findSportsSeasonsByStatus(status: SportsSeasonStatus): Pro }); } -export async function findAllSportsSeasons(): Promise { +export async function findAllSportsSeasons(): Promise { const db = database(); - return await db.query.sportsSeasons.findMany({ - orderBy: (sportsSeasons, { desc, asc }) => [desc(sportsSeasons.year), asc(sportsSeasons.name)], + const results = await db.query.sportsSeasons.findMany({ with: { sport: true, participants: { @@ -98,6 +100,16 @@ export async function findAllSportsSeasons(): Promise { }, }, }); + + const statusOrder: Record = { active: 0, upcoming: 1, completed: 2 }; + + return results.sort((a, b) => { + const statusDiff = statusOrder[a.status] - statusOrder[b.status]; + if (statusDiff !== 0) return statusDiff; + const sportNameDiff = a.sport.name.localeCompare(b.sport.name); + if (sportNameDiff !== 0) return sportNameDiff; + return a.year - b.year; + }); } export async function findDraftableSportsSeasons(): Promise { diff --git a/app/routes/admin.sports-seasons.tsx b/app/routes/admin.sports-seasons.tsx index 2f4d925..95c0000 100644 --- a/app/routes/admin.sports-seasons.tsx +++ b/app/routes/admin.sports-seasons.tsx @@ -27,15 +27,7 @@ export function meta(): Route.MetaDescriptors { export async function loader() { const sportsSeasons = await findAllSportsSeasons(); - // Type assertion since we know the sport and participants relations are included from the model - return { - sportsSeasons: sportsSeasons as Array< - typeof sportsSeasons[0] & { - sport: { id: string; name: string; type: string; slug: string }; - participants: Array<{ id: string }>; - } - > - }; + return { sportsSeasons }; } export default function AdminSportsSeasons({ loaderData }: Route.ComponentProps) {