// Admin managers for the Resources hub — Documents (file upload) + Calendar.
const HUBADMIN = (function () {
const fileSize = (bytes) => bytes < 1024 ? bytes + ' B' : bytes < 1048576 ? (bytes / 1024).toFixed(0) + ' KB' : (bytes / 1048576).toFixed(1) + ' MB';
const extOf = (name) => { const m = (name || '').match(/\.([a-z0-9]+)$/i); return m ? m[1].toUpperCase() : 'FILE'; };
function Dialog({ title, onClose, children, footer }) {
return (
e.stopPropagation()} className="fade-in" style={{ background: '#fff', borderRadius: 14, width: 'min(560px,100%)', maxHeight: '88vh', overflow: 'auto', boxShadow: 'var(--shadow-lg)' }}>
{title}
{children}
{footer}
);
}
// ---------------- Documents ----------------
function DocForm({ item, onClose }) {
const { data, actions } = CTSCStore.useStore();
const { DOC_CATS } = CTSCHub;
const [v, setV] = React.useState(item ? { ...item } : { title: '', category: 'Constitution', fedId: '', date: '2026-06-09', note: '', ext: '', size: '', data: '' });
const set = (k, val) => setV(s => ({ ...s, [k]: val }));
const ref = React.useRef();
const pick = (e) => {
const f = e.target.files[0]; if (!f) return;
const r = new FileReader();
r.onload = () => setV(s => ({ ...s, data: r.result, ext: extOf(f.name), size: fileSize(f.size), title: s.title || f.name.replace(/\.[a-z0-9]+$/i, '') }));
r.readAsDataURL(f);
};
const save = () => {
const payload = { ...v, fedId: v.fedId || null, status: 'Published' };
if (item) actions.updateItem('documents', item.id, payload); else actions.addItem('documents', payload);
onClose();
};
const valid = v.title.trim() && (v.data || item);
return (
);
}
function DocumentsManager() {
const { data, actions } = CTSCStore.useStore();
const { fmtDate, fedName } = CTSCStore;
const { DOC_CATS, DOC_META } = CTSCHub;
const [editing, setEditing] = React.useState(null);
const [cat, setCat] = React.useState('All');
const docs = (data.documents || []).filter(d => cat === 'All' || d.category === cat).sort((a, b) => (a.date < b.date ? 1 : -1));
return (
Documents
Publish documents for federations to view — constitutions, notices, agendas, minutes, correspondence & policies.
{['All', ...DOC_CATS].map(c => )}
{docs.length === 0 ? (
☷
No documents yet
Upload your first document for federations.
) : (
{docs.map(d => {
const meta = DOC_META[d.category] || { ic: '◆', c: 'var(--navy)' };
return (
{meta.ic}
{(d.ext || 'PDF').toUpperCase()}
{d.title}
{d.category} · {d.fedId ? fedName(data, d.fedId) : 'All federations'} · {fmtDate(d.date)}{d.size ? ' · ' + d.size : ''}
↓
);
})}
)}
{editing &&
setEditing(null)} />}
);
}
// ---------------- Calendar ----------------
function CalForm({ item, onClose }) {
const { data, actions } = CTSCStore.useStore();
const { CAL_TYPES } = CTSCHub;
const [v, setV] = React.useState(item ? { ...item } : { title: '', type: 'Meeting', date: '2026-06-09', time: '18:00', end: '', venue: '', fedId: '', desc: '' });
const set = (k, val) => setV(s => ({ ...s, [k]: val }));
const save = () => {
const payload = { ...v, fedId: v.fedId || null };
if (item) actions.updateItem('calendar', item.id, payload); else actions.addItem('calendar', payload);
onClose();
};
const valid = v.title.trim() && v.date;
return (
);
}
function CalendarManager() {
const { data, actions } = CTSCStore.useStore();
const { fmtDate } = CTSCStore;
const { TYPE_META, MonthCalendar } = CTSCHub;
const today = new Date();
const [month, setMonth] = React.useState(new Date(today.getFullYear(), today.getMonth(), 1));
const [editing, setEditing] = 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));
return (
Calendar
Plot meetings and workshops. Published to the Resources page for federations.
setMonth(m => new Date(m.getFullYear(), m.getMonth() - 1, 1))}
onNext={() => setMonth(m => new Date(m.getFullYear(), m.getMonth() + 1, 1))} />
{Object.entries(TYPE_META).map(([k, v]) => (
{v.label}
))}
Scheduled items
{upcoming.length} upcoming
{cal.map(e => (
{e.title}
{fmtDate(e.date)}{e.time ? ' · ' + e.time + (e.end ? '–' + e.end : '') : ''}{e.venue ? ' · ' + e.venue : ''}
{e.type}
))}
{editing && setEditing(null)} />}
);
}
return { DocumentsManager, CalendarManager };
})();
window.DocumentsManager = HUBADMIN.DocumentsManager;
window.CalendarManager = HUBADMIN.CalendarManager;