// Federation Resources hub — document library + meetings/workshops calendar (public, federation-facing). const CTSCHub = (function () { const DOC_CATS = ['Constitution', 'Meeting Notice', 'Agenda', 'Minutes', 'Correspondence', 'Policy']; const DOC_META = { 'Constitution': { ic: '§', c: 'var(--navy)' }, 'Meeting Notice': { ic: '◷', c: 'var(--gold-d)' }, 'Agenda': { ic: '☷', c: 'var(--ocean)' }, 'Minutes': { ic: '✎', c: 'var(--ok)' }, 'Correspondence': { ic: '✉', c: 'var(--warn)' }, 'Policy': { ic: '◆', c: 'var(--navy2)' }, }; const TYPE_META = { 'Meeting': { c: 'var(--navy)', label: 'Meeting' }, 'Workshop': { c: 'var(--ocean)', label: 'Workshop' }, 'AGM': { c: 'var(--gold)', label: 'AGM' }, 'Deadline': { c: 'var(--warn)', label: 'Deadline' }, 'Event': { c: 'var(--ok)', label: 'Event' }, }; const CAL_TYPES = ['Meeting', 'Workshop', 'AGM', 'Deadline', 'Event']; const extColor = (ext) => ({ PDF: '#b21f1f', DOCX: '#1453a6', DOC: '#1453a6', XLSX: '#1f8a5b', XLS: '#1f8a5b' }[(ext || '').toUpperCase()] || 'var(--muted)'); const WD = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const ymd = (d) => d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); // ---- Reusable month calendar grid ---- function MonthCalendar({ events, month, onPrev, onNext, selected, onSelect }) { const first = new Date(month.getFullYear(), month.getMonth(), 1); const lead = (first.getDay() + 6) % 7; // Mon=0 const daysIn = new Date(month.getFullYear(), month.getMonth() + 1, 0).getDate(); const todayStr = ymd(new Date()); const byDay = {}; events.forEach(e => { (byDay[e.date] = byDay[e.date] || []).push(e); }); const cells = []; for (let i = 0; i < lead; i++) cells.push(null); for (let d = 1; d <= daysIn; d++) cells.push(d); while (cells.length % 7 !== 0) cells.push(null); return (
{MONTHS[month.getMonth()]} {month.getFullYear()}
{WD.map(w =>
{w}
)}
{cells.map((d, i) => { if (d === null) return
; const ds = ymd(new Date(month.getFullYear(), month.getMonth(), d)); const evs = byDay[ds] || []; const isToday = ds === todayStr; const isSel = ds === selected; return ( ); })}
); } return { DOC_CATS, DOC_META, TYPE_META, CAL_TYPES, extColor, MonthCalendar, MONTHS, ymd }; })(); window.CTSCHub = CTSCHub; // ---- Public Resources page ---- function ResourcesPage() { const { data } = CTSCStore.useStore(); const { fmtDate, fedName } = CTSCStore; const { DOC_CATS, DOC_META, TYPE_META, MonthCalendar, MONTHS } = CTSCHub; const [view, setView] = React.useState('documents'); // documents const [q, setQ] = React.useState(''); const [cat, setCat] = React.useState('All'); const docs = (data.documents || []).filter(d => d.status !== 'Draft'); const filtered = docs.filter(d => { if (cat !== 'All' && d.category !== cat) return false; if (q.trim() && !((d.title + ' ' + (d.note || '')).toLowerCase().includes(q.toLowerCase().trim()))) return false; return true; }).sort((a, b) => (a.date < b.date ? 1 : -1)); // calendar const today = new Date(); const [month, setMonth] = React.useState(new Date(today.getFullYear(), today.getMonth(), 1)); const [sel, setSel] = React.useState(null); const cal = (data.calendar || []).slice().sort((a, b) => (a.date + (a.time || '')).localeCompare(b.date + (b.time || ''))); const upcoming = cal.filter(e => e.date >= CTSCHub.ymd(today)).slice(0, 6); const selEvents = sel ? cal.filter(e => e.date === sel) : null; return (
{/* hero */}
Federation Resources

Documents & Calendar

Official documents the Council shares with affiliated federations — constitutions, meeting notices, agendas, minutes and correspondence — plus the calendar of meetings and workshops.

{[['documents', '☷ Documents'], ['calendar', '◷ Calendar']].map(([id, label]) => ( ))}
{view === 'documents' ? (
{/* toolbar */}
setQ(e.target.value)} placeholder="Search documents…" style={{ width: '100%', fontFamily: 'inherit', fontSize: 15, color: 'var(--navy)', padding: '12px 14px 12px 38px', border: '1.5px solid var(--line)', borderRadius: 7, background: '#fff', outline: 'none' }} />
{filtered.length} document{filtered.length !== 1 ? 's' : ''}
{['All', ...DOC_CATS].map(c => )}
{/* list */} {filtered.length === 0 ? (
No documents match your filters
) : (
{filtered.map(d => { const meta = DOC_META[d.category] || { ic: '◆', c: 'var(--navy)' }; return (
{meta.ic} {(d.ext || 'PDF').toUpperCase()}
{d.title} {d.category}
{d.note &&
{d.note}
}
{d.fedId ? fedName(data, d.fedId) : 'All federations'} · {fmtDate(d.date)} · {d.size || '—'}
↓ Download
); })}
)}
) : (
setMonth(m => new Date(m.getFullYear(), m.getMonth() - 1, 1))} onNext={() => setMonth(m => new Date(m.getFullYear(), m.getMonth() + 1, 1))} selected={sel} onSelect={setSel} />
{Object.entries(TYPE_META).map(([k, v]) => (
{v.label}
))}
{sel ? CTSCStore.fmtDate(sel) : 'Upcoming'}
{(selEvents || upcoming).length === 0 ? (

{sel ? 'Nothing scheduled this day.' : 'No upcoming items.'}

) : (
{(selEvents || upcoming).map(e => (
{e.title}
{!sel && fmtDate(e.date)}{!sel && (e.time ? ' · ' : '')}{e.time && (e.time + (e.end ? '–' + e.end : ''))}
{e.venue &&
⚲ {e.venue}
} {TYPE_META[e.type].label}
))}
)} {sel && }
)}
); } window.ResourcesPage = ResourcesPage;