feat: sort admin sports seasons by status, sport name, then year (#286)

Active seasons sort first, then upcoming, then completed. Within each
status group, seasons sort by sport name alphabetically then year
ascending. Fixes return type of findAllSportsSeasons to reflect the
included sport/participants relations, removing the cast in the loader.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-04-09 10:07:18 -04:00 committed by GitHub
parent 68b2e070e6
commit dc879e3033
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 52 additions and 15 deletions

View file

@ -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", () => {

View file

@ -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<SportsSeason[]> {
export async function findAllSportsSeasons(): Promise<SportsSeasonListItem[]> {
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<SportsSeason[]> {
},
},
});
const statusOrder: Record<SportsSeasonStatus, number> = { 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<SportsSeason[]> {

View file

@ -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) {