Consolidate into one chronological list, tag each event by circuit

Replaces the one-section-per-circuit layout with a single flat,
deduped, date-sorted feed. Events belonging to more than one watched
circuit (real and not rare — verified 2 current events span both a
regional circuit and the national league) get one card carrying every
circuit badge that matched, rather than appearing once per section.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
This commit is contained in:
2026-09-04 17:08:11 -05:00
co-authored by Claude Sonnet 5
parent 463371d199
commit 4bb5dc8ed8
2 changed files with 44 additions and 31 deletions
+2 -2
View File
@@ -37,8 +37,8 @@
</div> </div>
</div> </div>
<!-- Circuit sections get built here, one per entry in CONFIG.circuits --> <!-- One consolidated, deduped, chronologically-sorted event list -->
<div class="hidden space-y-10" id="circuits"></div> <div class="hidden" id="events"></div>
<div class="divider mt-8"></div> <div class="divider mt-8"></div>
+42 -29
View File
@@ -240,12 +240,17 @@ function setupFilters() {
btn.classList.toggle("opacity-40", !nowActive); btn.classList.toggle("opacity-40", !nowActive);
if (nowActive) set.add(btn.dataset.filterValue); if (nowActive) set.add(btn.dataset.filterValue);
else set.delete(btn.dataset.filterValue); else set.delete(btn.dataset.filterValue);
renderCircuits(); renderEvents();
}); });
}); });
} }
function eventCard(ev) { // A single event can belong to more than one watched circuit (BCP events
// carry their own list of leagues, and it's common for e.g. a national
// league and a regional circuit to both include the same event) — badge
// it with every circuit it matched under rather than showing it once
// per circuit.
function eventCard(ev, circuits) {
const capacity = ev.numTickets const capacity = ev.numTickets
? `<span class="badge badge-ghost badge-sm">${ev.checkedInPlayers ?? 0}/${ev.numTickets} checked in</span>` ? `<span class="badge badge-ghost badge-sm">${ev.checkedInPlayers ?? 0}/${ev.numTickets} checked in</span>`
: ""; : "";
@@ -255,6 +260,7 @@ function eventCard(ev) {
const format = `<span class="badge badge-warning badge-sm">${formatOf(ev)}</span>`; const format = `<span class="badge badge-warning badge-sm">${formatOf(ev)}</span>`;
const scope = scopeOf(ev); const scope = scopeOf(ev);
const scopeBadge = scope ? `<span class="badge badge-info badge-sm">${scope}</span>` : ""; const scopeBadge = scope ? `<span class="badge badge-info badge-sm">${scope}</span>` : "";
const circuitBadges = circuits.map(c => `<span class="badge badge-accent badge-sm">${esc(c.name)}</span>`).join("");
return ` return `
<a href="${BCP_ORIGIN}/event/${esc(ev.id)}" target="_blank" rel="noopener" <a href="${BCP_ORIGIN}/event/${esc(ev.id)}" target="_blank" rel="noopener"
class="card card-border bg-base-200 hover:border-warning transition-colors"> class="card card-border bg-base-200 hover:border-warning transition-colors">
@@ -262,7 +268,7 @@ function eventCard(ev) {
<div class="min-w-0"> <div class="min-w-0">
<div class="font-semibold truncate">${esc(ev.name)}</div> <div class="font-semibold truncate">${esc(ev.name)}</div>
<div class="text-sm opacity-60 mt-1">${esc(locationOf(ev))}</div> <div class="text-sm opacity-60 mt-1">${esc(locationOf(ev))}</div>
<div class="flex gap-2 mt-2">${format}${scopeBadge}${system}${capacity}</div> <div class="flex gap-2 mt-2 flex-wrap">${format}${scopeBadge}${circuitBadges}${system}${capacity}</div>
</div> </div>
<div class="text-right shrink-0"> <div class="text-right shrink-0">
<div class="font-mono text-sm text-warning">${fmtDateRange(ev)}</div> <div class="font-mono text-sm text-warning">${fmtDateRange(ev)}</div>
@@ -271,36 +277,43 @@ function eventCard(ev) {
</a>`; </a>`;
} }
function circuitSection(circuit, events, filtered) { // Dedupe by event id across all circuits, collecting every circuit an
let body; // event matched under, then sort the merged list chronologically —
if (!events.length) { // replaces the old one-section-per-circuit layout with a single feed.
body = `<p class="text-sm text-base-content/50 py-4">No upcoming events for this circuit.</p>`; function mergeEvents(results) {
} else if (!filtered.length) { const byId = new Map();
body = `<p class="text-sm text-base-content/50 py-4">No events match the current filter.</p>`; for (const { circuit, events, error } of results) {
} else { if (error) continue;
body = filtered.map(eventCard).join(""); for (const ev of events) {
if (!byId.has(ev.id)) byId.set(ev.id, { event: ev, circuits: [] });
byId.get(ev.id).circuits.push(circuit);
} }
return ` }
<section> return [...byId.values()].sort((a, b) => Date.parse(a.event.eventDate) - Date.parse(b.event.eventDate));
<h2 class="text-2xl font-bold uppercase tracking-wide mb-3">
<a href="${BCP_ORIGIN}/circuit/${esc(circuit.id)}/calendar" target="_blank" rel="noopener" class="hover:text-warning">
${esc(circuit.name)}
</a>
</h2>
<div class="space-y-3">${body}</div>
</section>`;
} }
/* ---- Orchestration ---- */ /* ---- Orchestration ---- */
let allResults = []; let allResults = [];
function renderCircuits() { function renderEvents() {
$("circuits").innerHTML = allResults.map(({ circuit, events, error }) => const merged = mergeEvents(allResults);
error const filtered = merged.filter(({ event }) => passesFilter(event));
? `<section><h2 class="text-2xl font-bold uppercase tracking-wide mb-3">${esc(circuit.name)}</h2>
<p class="text-sm text-error py-4">Couldn't load events for this circuit — try refreshing.</p></section>` const failed = allResults.filter(r => r.error).map(r => r.circuit.name);
: circuitSection(circuit, events, events.filter(passesFilter)) const errorBanner = failed.length
).join(""); ? `<p class="text-sm text-error mb-4">Couldn't load: ${failed.map(esc).join(", ")} — try refreshing.</p>`
: "";
let body;
if (!merged.length) {
body = `<p class="text-sm text-base-content/50 py-4">No upcoming events found.</p>`;
} else if (!filtered.length) {
body = `<p class="text-sm text-base-content/50 py-4">No events match the current filter.</p>`;
} else {
body = `<div class="space-y-3">${filtered.map(({ event, circuits }) => eventCard(event, circuits)).join("")}</div>`;
}
$("events").innerHTML = errorBanner + body;
} }
async function load() { async function load() {
@@ -317,8 +330,8 @@ async function load() {
); );
$("loading").classList.add("hidden"); $("loading").classList.add("hidden");
$("circuits").classList.remove("hidden"); $("events").classList.remove("hidden");
renderCircuits(); renderEvents();
} }
setupFilters(); setupFilters();