Clean up the schema: drop unused tables, rename scopes, publishable awards
Migrations 020–023, with the code that reads each: - 020 drops people_lists, people_list_members, v_chapters and v_person_affiliations. Nothing queried any of them. - 021 renames event_sections to event_scopes and events.section_id to scope_id, finishing what 015 described. The API sends `scopes` and `scope_id`, and useEvents, EventListCards and EventCalendar take `scope`. It also inserts national/regional/partner, which only the retired seed ever created: a database built from migrations alone had no scope for the Retreats bands. - 022 drops events.sort_order and people.sort_order. Events now sort by date (upcoming soonest first, past latest first, undated last) on /events, org pages and the countdown. People were only ever sorted by sort_name on the site. Every other sort_order stays. - 023 rebuilds teams with created_at and updated_at plus a touch trigger, so the teams editor gets the same optimistic concurrency as the other entities. Awards can be drafted: is_published (added in 011) is on both descriptor halves with a Publishing group, and the award list, award page, org awards and event awards leave drafts out. The migration runner now turns foreign keys off around the per-file transactions and runs foreign_key_check before each commit. PRAGMA foreign_keys is a no-op inside a transaction, so 009's warning was right and a rebuild of a referenced table (023) couldn't be written otherwise. CLAUDE.md is updated to match. Also removes the stray src/App.tsx.save. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8790f861fe
commit
25e592bfea
19 changed files with 339 additions and 423 deletions
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<EventCalendar /> everything
|
||||
<EventCalendar host="northwest" /> one host's calendar
|
||||
<EventCalendar section="national" /> one scope
|
||||
<EventCalendar scope="national" /> one scope
|
||||
<EventCalendar type={["class", "workshop"]} controls={["search"]} />
|
||||
|
||||
Two layers of filtering, and they answer different questions:
|
||||
|
|
@ -63,7 +63,7 @@ const BODY = '#4a6b72'
|
|||
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
|
||||
type EventCalendarProps = {
|
||||
section?: string
|
||||
scope?: string
|
||||
host?: string
|
||||
status?: EventListItem['status']
|
||||
type?: EventType | EventType[]
|
||||
|
|
@ -223,7 +223,7 @@ function nextDateAfter(events: EventListItem[], after: string): string | null {
|
|||
/* ── Component ───────────────────────────────────────────────── */
|
||||
|
||||
export default function EventCalendar({
|
||||
section,
|
||||
scope,
|
||||
host,
|
||||
status,
|
||||
type,
|
||||
|
|
@ -231,32 +231,32 @@ export default function EventCalendar({
|
|||
controls = ALL_CONTROLS,
|
||||
defaultView = 'month',
|
||||
}: EventCalendarProps) {
|
||||
const { events: pinned, sections, loading, error } = useEvents({ section, host, status, type })
|
||||
const { events: pinned, scopes, loading, error } = useEvents({ scope, host, status, type })
|
||||
|
||||
const today = iso(new Date())
|
||||
const [month, setMonth] = useState(monthKey(today))
|
||||
const [view, setView] = useState<CalendarView>(defaultView)
|
||||
const [selected, setSelected] = useState<string | null>(null)
|
||||
|
||||
const [scope, setScope] = useState('')
|
||||
const [pickedScope, setPickedScope] = useState('')
|
||||
const [kind, setKind] = useState('')
|
||||
const [onlineOnly, setOnlineOnly] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
const show = (control: CalendarControl) => controls.includes(control)
|
||||
const showScope = show('scope') && !section
|
||||
const showScope = show('scope') && !scope
|
||||
const showType = show('type') && !type
|
||||
|
||||
const types = useMemo(() => typesPresent(pinned, EVENT_TYPES), [pinned])
|
||||
const scopes = useMemo(() => {
|
||||
const present = new Set(pinned.map((e) => e.section_id))
|
||||
return sections.filter((s) => present.has(s.id))
|
||||
}, [pinned, sections])
|
||||
const scopeOptions = useMemo(() => {
|
||||
const present = new Set(pinned.map((e) => e.scope_id))
|
||||
return scopes.filter((s) => present.has(s.id))
|
||||
}, [pinned, scopes])
|
||||
|
||||
const events = useMemo(() => {
|
||||
const needle = query.trim().toLowerCase()
|
||||
return pinned.filter((e) => {
|
||||
if (scope && e.section_id !== scope) return false
|
||||
if (pickedScope && e.scope_id !== pickedScope) return false
|
||||
if (kind && e.event_type !== kind) return false
|
||||
if (onlineOnly && !e.is_online) return false
|
||||
if (needle) {
|
||||
|
|
@ -274,7 +274,7 @@ export default function EventCalendar({
|
|||
}
|
||||
return true
|
||||
})
|
||||
}, [pinned, scope, kind, onlineOnly, query])
|
||||
}, [pinned, pickedScope, kind, onlineOnly, query])
|
||||
|
||||
const weeks = useMemo(() => gridFor(month), [month])
|
||||
const gridFrom = weeks[0][0]
|
||||
|
|
@ -288,7 +288,7 @@ export default function EventCalendar({
|
|||
)
|
||||
const inMonth = occurrences.filter((o) => o.end >= monthFrom && o.start <= monthTo)
|
||||
const undated = events.filter((e) => !e.starts_on).length
|
||||
const filtering = Boolean(scope || kind || onlineOnly || query)
|
||||
const filtering = Boolean(pickedScope || kind || onlineOnly || query)
|
||||
const next = inMonth.length === 0 ? nextDateAfter(events, monthTo) : null
|
||||
|
||||
const onDay = (date: string) => occurrences.filter((o) => o.start <= date && o.end >= date)
|
||||
|
|
@ -299,7 +299,7 @@ export default function EventCalendar({
|
|||
}
|
||||
|
||||
const clearFilters = () => {
|
||||
setScope('')
|
||||
setPickedScope('')
|
||||
setKind('')
|
||||
setOnlineOnly(false)
|
||||
setQuery('')
|
||||
|
|
@ -361,13 +361,13 @@ export default function EventCalendar({
|
|||
{/* ── Filters ── */}
|
||||
{(showScope || showType || show('online') || show('search')) && (
|
||||
<div className="mt-5 flex flex-wrap items-center gap-3">
|
||||
{showScope && scopes.length > 1 && (
|
||||
{showScope && scopeOptions.length > 1 && (
|
||||
<FilterSelect
|
||||
label="Scope"
|
||||
value={scope}
|
||||
onChange={setScope}
|
||||
value={pickedScope}
|
||||
onChange={setPickedScope}
|
||||
all="All scopes"
|
||||
options={scopes.map((s) => [s.id, s.name])}
|
||||
options={scopeOptions.map((s) => [s.id, s.name])}
|
||||
/>
|
||||
)}
|
||||
{showType && types.length > 1 && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue