/** Reine Hilfen für Trainingsplanung (Kalender, Sichtbarkeits-Kurztext, Trainer-Zuordnung). */ export function trainingVisibilityShortDE(visibility) { const v = String(visibility || '').trim().toLowerCase() if (v === 'official') return 'Öffentliche Bibliothek' if (v === 'club') return 'Verein' if (v === 'private') return 'Privat' return visibility ? String(visibility) : '' } export function addDaysIsoDate(isoDay, daysDelta) { const d = new Date(`${isoDay}T12:00:00`) d.setDate(d.getDate() + daysDelta) return d.toISOString().slice(0, 10) } export function pad2(n) { return String(n).padStart(2, '0') } export function toIsoLocal(d) { return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` } /** Montag = erster Wochentag (ISO-Woche UI) */ export function mondayIndex(d) { return (d.getDay() + 6) % 7 } /** Kalendarische Monatsansicht: erster und letzter Tag des sichtbaren Rasters (Mo–So) */ export function getCalendarGridRange(ym) { const parts = (ym || '').split('-').map(Number) const y = parts[0] const m = parts[1] if (!y || !m || m < 1 || m > 12) { const t = new Date() return { gridStart: toIsoLocal(t), gridEnd: toIsoLocal(t) } } const first = new Date(y, m - 1, 1) const last = new Date(y, m, 0) const gridStart = new Date(first) gridStart.setDate(first.getDate() - mondayIndex(first)) const lastMon = mondayIndex(last) const gridEnd = new Date(last) gridEnd.setDate(last.getDate() + (6 - lastMon)) return { gridStart: toIsoLocal(gridStart), gridEnd: toIsoLocal(gridEnd) } } export function shiftCalendarMonth(ym, delta) { const parts = (ym || '').split('-').map(Number) const y = parts[0] || new Date().getFullYear() const m = parts[1] || 1 const d = new Date(y, m - 1 + delta, 1) return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}` } export function enumerateIsoDays(fromIso, toIso) { const out = [] const cur = new Date(`${fromIso}T12:00:00`) const end = new Date(`${toIso}T12:00:00`) while (cur <= end) { out.push(toIsoLocal(cur)) cur.setDate(cur.getDate() + 1) } return out } export const WEEKDAYS_DE = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'] export function toNumList(arr) { if (!Array.isArray(arr)) return [] const out = [] for (const x of arr) { const n = Number(x) if (Number.isFinite(n) && n >= 1) out.push(n) } return out } export const sessionAssignDefaults = () => ({ lead_trainer_profile_id: '', session_assistants_inherit: true, session_assistant_profile_ids: [], }) /** Co_trainer_ids aus TrainingGroups (Liste/JSON) → Zahlenliste */ export function normalizeGroupCoTrainerIds(raw) { if (raw == null) return [] const arr = Array.isArray(raw) ? raw : [] const out = [] for (const x of arr) { const n = Number(x) if (Number.isFinite(n) && n >= 1) out.push(n) } return out } /** Mitgliederverzeichnis-Einträge ohne effektiven Leitungsträger als Co‑Option */ export function filterDirectoryExcludingLead(directory, excludeLeadPid) { const ex = excludeLeadPid != null && excludeLeadPid !== '' && Number.isFinite(Number(excludeLeadPid)) ? Number(excludeLeadPid) : null if (ex == null) return directory return directory.filter((m) => Number(m.id) !== ex) } /** Kurztexte für Rahmen-Herkunft (Listen + Formular-Modal). */ export function frameworkLineageText(unit) { const fpTitle = (unit.origin_framework_program_title || '').trim() || 'Rahmenprogramm' const st = (unit.origin_framework_slot_title || '').trim() const idx = unit.origin_framework_slot_sort_order const slotBit = st || (typeof idx === 'number' ? `Session ${idx + 1}` : 'Session') return { fpTitle, slotBit, fpId: unit.origin_framework_program_id } }