Hide completed seasons in admin tools (#418)
This commit is contained in:
parent
5b261bf258
commit
ce0ed4f485
7 changed files with 154 additions and 9 deletions
|
|
@ -8,7 +8,8 @@ vi.mock("~/services/simulations/runner", () => ({
|
|||
runSportsSeasonSimulation: vi.fn(),
|
||||
}));
|
||||
|
||||
import { action } from "../admin.simulators";
|
||||
import { action, loader } from "../admin.simulators";
|
||||
import { listSportsSeasonSimulatorSummaries } from "~/models/simulator";
|
||||
import { runSportsSeasonSimulation } from "~/services/simulations/runner";
|
||||
|
||||
function postForm(entries: Record<string, string | string[]>) {
|
||||
|
|
@ -31,6 +32,76 @@ beforeEach(() => {
|
|||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("admin simulators loader", () => {
|
||||
it("hides completed seasons", async () => {
|
||||
vi.mocked(listSportsSeasonSimulatorSummaries).mockResolvedValue([
|
||||
{
|
||||
sportsSeasonId: "season-upcoming",
|
||||
seasonName: "2026",
|
||||
year: 2026,
|
||||
seasonStatus: "upcoming",
|
||||
simulationStatus: "idle",
|
||||
fantasySeasonId: null,
|
||||
fantasySeasonName: null,
|
||||
leagueName: null,
|
||||
sportName: "NBA",
|
||||
sportSlug: "nba",
|
||||
simulatorType: "nba_bracket",
|
||||
simulatorName: "NBA Bracket",
|
||||
participantCount: 16,
|
||||
participantInputCount: 16,
|
||||
lastSimulatedDate: null,
|
||||
readiness: {
|
||||
status: "ready",
|
||||
canRun: true,
|
||||
participantInputCount: 16,
|
||||
participantCount: 16,
|
||||
missingInputs: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
sportsSeasonId: "season-completed",
|
||||
seasonName: "2025",
|
||||
year: 2025,
|
||||
seasonStatus: "completed",
|
||||
simulationStatus: "idle",
|
||||
fantasySeasonId: null,
|
||||
fantasySeasonName: null,
|
||||
leagueName: null,
|
||||
sportName: "NFL",
|
||||
sportSlug: "nfl",
|
||||
simulatorType: "nfl_bracket",
|
||||
simulatorName: "NFL Bracket",
|
||||
participantCount: 14,
|
||||
participantInputCount: 14,
|
||||
lastSimulatedDate: "2026-05-11",
|
||||
readiness: {
|
||||
status: "ready",
|
||||
canRun: true,
|
||||
participantInputCount: 14,
|
||||
participantCount: 14,
|
||||
missingInputs: [],
|
||||
warnings: [],
|
||||
},
|
||||
},
|
||||
] as never);
|
||||
|
||||
const response = await loader();
|
||||
|
||||
expect(response).toEqual({
|
||||
simulators: [
|
||||
expect.objectContaining({
|
||||
sportsSeasonId: "season-upcoming",
|
||||
seasonStatus: "upcoming",
|
||||
}),
|
||||
],
|
||||
sports: ["NBA"],
|
||||
simulatorTypes: ["nba_bracket"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("admin simulators action", () => {
|
||||
it("runs one simulator", async () => {
|
||||
vi.mocked(runSportsSeasonSimulation).mockResolvedValue({
|
||||
|
|
|
|||
22
app/routes/__tests__/admin.sports-seasons.helpers.test.ts
Normal file
22
app/routes/__tests__/admin.sports-seasons.helpers.test.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { filterVisibleSportsSeasons } from "../admin.sports-seasons.helpers";
|
||||
|
||||
describe("filterVisibleSportsSeasons", () => {
|
||||
const sportsSeasons = [
|
||||
{ id: "season-1", status: "upcoming" },
|
||||
{ id: "season-2", status: "active" },
|
||||
{ id: "season-3", status: "completed" },
|
||||
];
|
||||
|
||||
it("hides completed seasons when the filter is enabled", () => {
|
||||
expect(filterVisibleSportsSeasons(sportsSeasons, true)).toEqual([
|
||||
{ id: "season-1", status: "upcoming" },
|
||||
{ id: "season-2", status: "active" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("shows completed seasons when the filter is disabled", () => {
|
||||
expect(filterVisibleSportsSeasons(sportsSeasons, false)).toEqual(sportsSeasons);
|
||||
});
|
||||
});
|
||||
|
|
@ -27,7 +27,8 @@ export function meta(): Route.MetaDescriptors {
|
|||
}
|
||||
|
||||
export async function loader() {
|
||||
const simulators = await listSportsSeasonSimulatorSummaries();
|
||||
const simulators = (await listSportsSeasonSimulatorSummaries())
|
||||
.filter((simulator) => simulator.seasonStatus !== "completed");
|
||||
const sports = [...new Set(simulators.map((sim) => sim.sportName))].toSorted();
|
||||
const simulatorTypes = [...new Set(simulators.map((sim) => sim.simulatorType))].toSorted();
|
||||
return { simulators, sports, simulatorTypes };
|
||||
|
|
@ -159,7 +160,6 @@ export default function AdminSimulators({ loaderData }: Route.ComponentProps) {
|
|||
<option value="all">All season statuses</option>
|
||||
<option value="upcoming">Upcoming</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="completed">Completed</option>
|
||||
</select>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
7
app/routes/admin.sports-seasons.helpers.ts
Normal file
7
app/routes/admin.sports-seasons.helpers.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export function filterVisibleSportsSeasons<T extends { status: string }>(
|
||||
sportsSeasons: T[],
|
||||
hideCompleted: boolean
|
||||
): T[] {
|
||||
if (!hideCompleted) return sportsSeasons;
|
||||
return sportsSeasons.filter((season) => season.status !== "completed");
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { Link } from "react-router";
|
||||
import { Link, useSearchParams } from "react-router";
|
||||
import type { Route } from "./+types/admin.sports-seasons";
|
||||
|
||||
import { findAllAdminSportsSeasons } from "~/models/sports-season";
|
||||
import { filterVisibleSportsSeasons } from "./admin.sports-seasons.helpers";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
|
@ -10,6 +11,8 @@ import {
|
|||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { Switch } from "~/components/ui/switch";
|
||||
import { Plus, Calendar } from "lucide-react";
|
||||
import {
|
||||
Table,
|
||||
|
|
@ -32,7 +35,17 @@ export async function loader() {
|
|||
|
||||
export default function AdminSportsSeasons({ loaderData }: Route.ComponentProps) {
|
||||
const { sportsSeasons } = loaderData;
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const hideCompleted = searchParams.get("showCompleted") !== "true";
|
||||
const visibleSportsSeasons = filterVisibleSportsSeasons(sportsSeasons, hideCompleted);
|
||||
|
||||
function setHideCompleted(nextHideCompleted: boolean) {
|
||||
const next = new URLSearchParams(searchParams);
|
||||
if (nextHideCompleted) next.delete("showCompleted");
|
||||
else next.set("showCompleted", "true");
|
||||
setSearchParams(next);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
|
|
@ -53,10 +66,22 @@ export default function AdminSportsSeasons({ loaderData }: Route.ComponentProps)
|
|||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>All Sports Seasons</CardTitle>
|
||||
<CardDescription>
|
||||
{sportsSeasons.length} {sportsSeasons.length === 1 ? "season" : "seasons"} total
|
||||
</CardDescription>
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<CardTitle>All Sports Seasons</CardTitle>
|
||||
<CardDescription>
|
||||
{visibleSportsSeasons.length} of {sportsSeasons.length} {sportsSeasons.length === 1 ? "season" : "seasons"} shown
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Label htmlFor="hide-completed-seasons">Hide completed seasons</Label>
|
||||
<Switch
|
||||
id="hide-completed-seasons"
|
||||
checked={hideCompleted}
|
||||
onCheckedChange={setHideCompleted}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{sportsSeasons.length === 0 ? (
|
||||
|
|
@ -73,6 +98,14 @@ export default function AdminSportsSeasons({ loaderData }: Route.ComponentProps)
|
|||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
) : visibleSportsSeasons.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Calendar className="mx-auto h-12 w-12 text-muted-foreground" />
|
||||
<h3 className="mt-4 text-lg font-semibold">No visible sports seasons</h3>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
All current sports seasons are completed. Turn off the filter to review them.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
|
|
@ -88,7 +121,7 @@ export default function AdminSportsSeasons({ loaderData }: Route.ComponentProps)
|
|||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{sportsSeasons.map((season) => (
|
||||
{visibleSportsSeasons.map((season) => (
|
||||
<TableRow key={season.id}>
|
||||
<TableCell className="font-medium">{season.name}</TableCell>
|
||||
<TableCell>{season.sport.name}</TableCell>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import { normalizeSimulationResultColumns } from "~/services/simulations/simulat
|
|||
|
||||
const SEASON = {
|
||||
id: "season-1",
|
||||
status: "active",
|
||||
simulationStatus: "idle",
|
||||
fantasySeasonId: null,
|
||||
};
|
||||
|
|
@ -139,6 +140,14 @@ describe("runSportsSeasonSimulation", () => {
|
|||
await expect(runSportsSeasonSimulation("season-1")).rejects.toThrow("already running");
|
||||
});
|
||||
|
||||
it("throws when the sports season is completed", async () => {
|
||||
vi.mocked(findSportsSeasonById).mockResolvedValue({ ...SEASON, status: "completed" } as never);
|
||||
|
||||
await expect(runSportsSeasonSimulation("season-1")).rejects.toThrow("cannot be simulated");
|
||||
expect(prepareSimulatorInputsForRun).not.toHaveBeenCalled();
|
||||
expect(updateSportsSeason).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws when readiness check fails", async () => {
|
||||
vi.mocked(validateSimulatorReadiness).mockResolvedValue({
|
||||
canRun: false,
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ export async function runSportsSeasonSimulation(
|
|||
if (!sportsSeason) {
|
||||
throw new Error("Sports season not found");
|
||||
}
|
||||
if (sportsSeason.status === "completed") {
|
||||
throw new Error("Completed sports seasons cannot be simulated.");
|
||||
}
|
||||
|
||||
const simulatorConfig = await getSportsSeasonSimulatorConfig(sportsSeasonId);
|
||||
if (!simulatorConfig) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue