/** * Modul: RRULE UI-Helfer * Zweck: Wiederholungs-Formular (HTML + Logik) für Aufgaben- und Kalender-Modals * Abhängigkeiten: keine */ const FREQ_OPTIONS = [ { value: '', label: 'Keine Wiederholung' }, { value: 'DAILY', label: 'Täglich' }, { value: 'WEEKLY', label: 'Wöchentlich' }, { value: 'MONTHLY', label: 'Monatlich' }, ]; const WEEKDAYS = [ { value: 'MO', label: 'Mo' }, { value: 'TU', label: 'Di' }, { value: 'WE', label: 'Mi' }, { value: 'TH', label: 'Do' }, { value: 'FR', label: 'Fr' }, { value: 'SA', label: 'Sa' }, { value: 'SU', label: 'So' }, ]; /** * Parsed einen RRULE-String in ein Objekt für die UI. * @param {string|null} rule - z.B. "FREQ=WEEKLY;BYDAY=MO,TH;INTERVAL=2" * @returns {{ freq: string, interval: number, byday: string[], until: string }} */ export function parseRRule(rule) { const result = { freq: '', interval: 1, byday: [], until: '' }; if (!rule) return result; for (const segment of rule.split(';')) { const eq = segment.indexOf('='); if (eq === -1) continue; const key = segment.slice(0, eq).toUpperCase(); const val = segment.slice(eq + 1); if (key === 'FREQ') result.freq = val; if (key === 'INTERVAL') result.interval = parseInt(val, 10) || 1; if (key === 'BYDAY') result.byday = val.split(',').map(d => d.trim()); if (key === 'UNTIL') { // YYYYMMDD → YYYY-MM-DD const c = val.replace(/[TZ]/g, ''); result.until = `${c.slice(0, 4)}-${c.slice(4, 6)}-${c.slice(6, 8)}`; } } return result; } /** * Baut einen RRULE-String aus den UI-Werten. * @param {{ freq: string, interval: number, byday: string[], until: string }} opts * @returns {string|null} - RRULE-String oder null (keine Wiederholung) */ export function buildRRule({ freq, interval, byday, until }) { if (!freq) return null; const parts = [`FREQ=${freq}`]; if (interval > 1) parts.push(`INTERVAL=${interval}`); if (freq === 'WEEKLY' && byday.length > 0) { parts.push(`BYDAY=${byday.join(',')}`); } if (until) { parts.push(`UNTIL=${until.replace(/-/g, '')}T235959Z`); } return parts.join(';'); } /** * Rendert das HTML für die Wiederholungs-Felder. * @param {string} prefix - ID-Prefix (z.B. "task" oder "event") * @param {string|null} existingRule - bestehende RRULE oder null * @returns {string} HTML-String */ export function renderRRuleFields(prefix, existingRule) { const parsed = parseRRule(existingRule); const freqOpts = FREQ_OPTIONS.map(o => `` ).join(''); const dayBtns = WEEKDAYS.map(d => `` ).join(''); return `
${unitLabel(parsed.freq, parsed.interval)}
${dayBtns}
`; } function unitLabel(freq, interval) { const n = interval > 1; if (freq === 'DAILY') return n ? 'Tage' : 'Tag'; if (freq === 'WEEKLY') return n ? 'Wochen' : 'Woche'; if (freq === 'MONTHLY') return n ? 'Monate' : 'Monat'; return ''; } /** * Bindet Events an die RRULE-Felder (Freq-Change, Day-Toggle, etc.) * @param {HTMLElement} root - Container-Element * @param {string} prefix - ID-Prefix */ export function bindRRuleEvents(root, prefix) { const freqSelect = root.querySelector(`#${prefix}-rrule-freq`); const details = root.querySelector(`#${prefix}-rrule-details`); const weekdays = root.querySelector(`#${prefix}-rrule-weekdays`); const unitEl = root.querySelector(`#${prefix}-rrule-unit`); const intervalEl = root.querySelector(`#${prefix}-rrule-interval`); if (!freqSelect) return; freqSelect.addEventListener('change', () => { const freq = freqSelect.value; if (details) details.hidden = !freq; if (weekdays) weekdays.hidden = freq !== 'WEEKLY'; updateUnit(); }); intervalEl?.addEventListener('input', updateUnit); // Day-Toggle root.querySelectorAll(`#${prefix}-rrule-weekdays .rrule-day`).forEach(btn => { btn.addEventListener('click', () => { btn.classList.toggle('rrule-day--active'); btn.setAttribute('aria-pressed', btn.classList.contains('rrule-day--active')); }); }); function updateUnit() { if (!unitEl) return; const interval = parseInt(intervalEl?.value, 10) || 1; unitEl.textContent = unitLabel(freqSelect.value, interval); } } /** * Liest die aktuellen RRULE-Werte aus dem Formular. * @param {HTMLElement} root - Container-Element * @param {string} prefix - ID-Prefix * @returns {{ is_recurring: boolean, recurrence_rule: string|null }} */ export function getRRuleValues(root, prefix) { const freq = root.querySelector(`#${prefix}-rrule-freq`)?.value || ''; const interval = parseInt(root.querySelector(`#${prefix}-rrule-interval`)?.value, 10) || 1; const until = root.querySelector(`#${prefix}-rrule-until`)?.value || ''; const byday = []; root.querySelectorAll(`#${prefix}-rrule-weekdays .rrule-day--active`).forEach(btn => { byday.push(btn.dataset.day); }); const rule = buildRRule({ freq, interval, byday, until }); return { is_recurring: !!rule, recurrence_rule: rule, }; }