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:
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user