v1.5 - history and timeline as well as many datastructure updates added, polished, fixes

This commit is contained in:
Zaldimmar 2026-09-25 02:38:51 -05:00
parent 1f0aa3078f
commit 1d84400aef
63 changed files with 7927 additions and 208 deletions

View file

@ -7,10 +7,17 @@
narrows the result to what it shows.
useEvents({ section: "national" }) one band
useEvents({ host: "northwest" }) a region's own events
useEvents({ host: "northwest" }) one host's events
useEvents({ status: "upcoming" }) a home page strip
useEvents({ type: "workshop" }) one kind, wherever it is
useEvents({ type: ["class", "workshop"] })
useEvents() everything
`section` and `type` are different questions and stack rather
than overlap: the section is which band of the page an event
belongs to, the type is what kind of gathering it is. A regional
class matches both { section: "regional" } and { type: "class" }.
Filtering here rather than in the query keeps the endpoint to
one cached response. At a few dozen events that's the right
trade; if the list ever runs to hundreds, move the filters into
@ -23,18 +30,30 @@ import { useResource } from "../lib/useResource.js";
const EMPTY = { events: [] };
export function useEvents({ section, host, status } = {}) {
export function useEvents({ section, host, status, type } = {}) {
const { data, error, loading } = useResource("/events", { fallback: EMPTY });
const all = data?.events;
/* An array prop is a new identity on every render, which would
restart the memo each time. Joining it gives the dependency
list something stable to compare. */
const typeKey = Array.isArray(type) ? type.join(",") : (type ?? "");
const events = useMemo(() => {
let list = all ?? [];
if (section) list = list.filter(e => e.section_id === section);
if (host) list = list.filter(e => e.host?.id === host);
// `host` is an organization or a person slug, and an event can
// have several of either — co-hosting puts one event on both
// hosts' lists, which is the point.
if (host) list = list.filter(e => e.hosts?.some(h => h.id === host));
if (status) list = list.filter(e => e.status === status);
if (typeKey) {
const wanted = new Set(typeKey.split(","));
list = list.filter(e => wanted.has(e.event_type));
}
return list;
}, [all, section, host, status]);
}, [all, section, host, status, typeKey]);
return { events, loading, error };
}
@ -52,3 +71,12 @@ export function splitByStatus(events = []) {
return { upcoming, past };
}
/* Which of the declared types a list actually contains, in
EVENT_TYPES order rather than whatever order the rows arrived
in. A section with one type has nothing to filter, which is what
lets the chip bar hide itself. */
export function typesPresent(events = [], declared = []) {
const seen = new Set(events.map(e => e.event_type));
return declared.filter(entry => seen.has(entry.id));
}