Add recurring series to events

A Series checkbox under When opens a panel for the schedule:
weekly on chosen weekdays, monthly by date, or monthly by weekday
position, every N weeks or months, with meeting times and an
optional meeting count. starts_on anchors the schedule and ends_on
bounds it, so effective_status needs no change.

Occurrences are derived, not stored. src/lib/eventSeries.ts builds
the schedule label shown on cards and the event page, and the
upcoming dates listed on the event page.

Migration 016 adds the columns; the CRUD engine gains a time type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Zaldimmar 2026-09-25 04:46:49 -05:00
parent aebc1c302b
commit 5ed7994a64
11 changed files with 500 additions and 3 deletions

View file

@ -8,6 +8,7 @@ import {
} from "../../data/eventData.js";
import { eventHref } from "../../lib/hrefs.ts";
import { EVENT_TYPES, eventTypeLabel, type EventType } from "../../lib/eventTypes.ts";
import { seriesLabel } from "../../lib/eventSeries.ts";
import type { EventListItem } from "../../lib/useContent.ts";
import type { SectionToggleProps } from "../../lib/sections.tsx";
@ -195,6 +196,7 @@ export function Card({
const color = ev.color || defaultColor;
const orgLogo = ev.org_logo || DEFAULT_ORG_LOGO;
const eventLogo = ev.event_logo;
const schedule = seriesLabel(ev.series, ev.starts_on);
const links = ev.links ?? [];
const igHandle = ev.instagram || null;
const igUrl = igHandle
@ -255,6 +257,7 @@ export function Card({
<p className="text-xl font-300 font-bold">"{ev.theme}"</p>
)}
{ev.date_label && <p className="text-xl">{ev.date_label}</p>}
{schedule && <p className="text-xl">{schedule}</p>}
{ev.location_label && (
<p className="text-xl">{ev.location_label}</p>
)}
@ -282,6 +285,7 @@ export function Card({
<p className="text-2xl font-300 font-bold">"{ev.theme}"</p>
)}
{ev.date_label && <p className="text-2xl">{ev.date_label}</p>}
{schedule && <p className="text-2xl">{schedule}</p>}
{ev.location_label && (
<p className="text-2xl">{ev.location_label}</p>
)}