diff --git a/app/models/__tests__/scoring-event-dashboard.test.ts b/app/models/__tests__/scoring-event-dashboard.test.ts index 214c688..b2b631d 100644 --- a/app/models/__tests__/scoring-event-dashboard.test.ts +++ b/app/models/__tests__/scoring-event-dashboard.test.ts @@ -270,4 +270,55 @@ describe("getEventsForDates", () => { expect(result[0].displayDate).toBe("2026-03-20"); }); + + it("uses gameDate over eventDate when eventDate is outside requested dates", async () => { + // Bracket event: eventDate is the round's overall date (not in range), + // but an individual game is scheduled on the requested date. + mockSelect.mockReturnValue( + makeSelectChain([{ id: "e1", eventDate: "2026-04-05", gameDate: "2026-03-20" }]) + ); + mockFindMany.mockResolvedValueOnce([makeEventRow({ id: "e1", eventDate: "2026-04-05" })]); + + const result = await getEventsForDates(["2026-03-20"]); + + expect(result).toHaveLength(1); + expect(result[0].displayDate).toBe("2026-03-20"); + }); + + it("returns two entries for an event with games on both requested dates", async () => { + // Same event has a game today AND a game tomorrow — should appear in both tabs. + mockSelect.mockReturnValue( + makeSelectChain([ + { id: "e1", eventDate: null, gameDate: "2026-03-20" }, + { id: "e1", eventDate: null, gameDate: "2026-03-21" }, + ]) + ); + mockFindMany.mockResolvedValueOnce([makeEventRow({ id: "e1", eventDate: null })]); + + const result = await getEventsForDates(["2026-03-20", "2026-03-21"]); + + expect(result).toHaveLength(2); + const displayDates = result.map((e) => e.displayDate).toSorted(); + expect(displayDates).toEqual(["2026-03-20", "2026-03-21"]); + // Both entries share the same event id + expect(result[0].id).toBe("e1"); + expect(result[1].id).toBe("e1"); + }); + + it("deduplicates when multiple matches in same bracket have games on the same day", async () => { + // Two rows for the same event on the same gameDate (e.g. two matches in today's bracket) + mockSelect.mockReturnValue( + makeSelectChain([ + { id: "e1", eventDate: null, gameDate: "2026-03-20" }, + { id: "e1", eventDate: null, gameDate: "2026-03-20" }, + ]) + ); + mockFindMany.mockResolvedValueOnce([makeEventRow({ id: "e1", eventDate: null })]); + + const result = await getEventsForDates(["2026-03-20"]); + + // Should appear only once, not twice + expect(result).toHaveLength(1); + expect(result[0].displayDate).toBe("2026-03-20"); + }); }); diff --git a/app/models/scoring-event.ts b/app/models/scoring-event.ts index c77392f..1c900b3 100644 --- a/app/models/scoring-event.ts +++ b/app/models/scoring-event.ts @@ -665,14 +665,33 @@ export async function getEventsForDates( if (rows.length === 0) return []; - // Deduplicate: keep first matched row per event to derive displayDate. - const displayDateMap = new Map(); + // Collect ALL dates each event qualifies for. For bracket events, the + // game's scheduledAt date (gameDate) takes priority over the event-level + // eventDate — the event-level date might be set to the overall round date + // (e.g. "April 5") while an individual game is actually scheduled today. + // Using a Set per event also de-duplicates naturally when multiple matches + // in the same bracket have games on the same day. + const eventDatesMap = new Map>(); for (const row of rows) { - if (!displayDateMap.has(row.id)) { - displayDateMap.set(row.id, row.eventDate ?? row.gameDate ?? null); + if (!eventDatesMap.has(row.id)) { + eventDatesMap.set(row.id, new Set()); + } + const dateSet = eventDatesMap.get(row.id); + if (!dateSet) continue; + if (row.eventDate && dates.includes(row.eventDate)) { + dateSet.add(row.eventDate); + } + if (row.gameDate && dates.includes(row.gameDate)) { + dateSet.add(row.gameDate); + } + // Fallback: row passed the WHERE clause so at least one date is relevant; + // if neither mapped to a requested date (shouldn't happen), keep something. + if (dateSet.size === 0) { + const fallback = row.gameDate ?? row.eventDate ?? null; + if (fallback) dateSet.add(fallback); } } - const eventIds = [...displayDateMap.keys()]; + const eventIds = [...eventDatesMap.keys()]; // Step 2: fetch the matched events with sport season + sport info. const events = await db.query.scoringEvents.findMany({ @@ -691,20 +710,28 @@ export async function getEventsForDates( }, }); - return events.map((e) => ({ - id: e.id, - name: e.name, - eventDate: e.eventDate, - displayDate: displayDateMap.get(e.id) ?? e.eventDate ?? null, - eventStartsAt: e.eventStartsAt, - eventType: e.eventType, - isComplete: e.isComplete, - completedAt: e.completedAt, - sportsSeasonId: e.sportsSeasonId, - sportsSeasonName: e.sportsSeason.name, - sportName: e.sportsSeason.sport.name, - sportSlug: e.sportsSeason.sport.slug, - })); + // Return one entry per (event, date) pair so that an event with games on + // both today and tomorrow appears in both tabs of the dashboard widget. + return events.flatMap((e) => { + const matchingDates = [...(eventDatesMap.get(e.id) ?? [])].toSorted(); + const base = { + id: e.id, + name: e.name, + eventDate: e.eventDate, + eventStartsAt: e.eventStartsAt, + eventType: e.eventType, + isComplete: e.isComplete, + completedAt: e.completedAt, + sportsSeasonId: e.sportsSeasonId, + sportsSeasonName: e.sportsSeason.name, + sportName: e.sportsSeason.sport.name, + sportSlug: e.sportsSeason.sport.slug, + }; + if (matchingDates.length === 0) { + return [{ ...base, displayDate: e.eventDate ?? null }]; + } + return matchingDates.map((date) => ({ ...base, displayDate: date })); + }); } /**