/** * BreakPilot Studio - Learning Units Module * * Lerneinheiten-Verwaltung: * - Laden, Erstellen, Löschen von Lerneinheiten * - Zuordnung von Arbeitsblättern zu Lerneinheiten * - Filter für Lerneinheiten-spezifische Ansicht * * Refactored: 2026-01-19 */ import { t } from './i18n.js'; import { setStatus } from './api-helpers.js'; // State let units = []; let currentUnitId = null; let showOnlyUnitFiles = false; // DOM References let unitListEl = null; let unitHeading1 = null; let unitHeading2 = null; let btnAddUnit = null; let btnToggleFilter = null; let btnAttachCurrentToLu = null; // Form Inputs let unitStudentInput = null; let unitSubjectInput = null; let unitGradeInput = null; let unitTitleInput = null; // Callbacks für File-Manager Integration let getEingangFilesCallback = null; let getAllEingangFilesCallback = null; let getAllWorksheetPairsCallback = null; let setFilteredDataCallback = null; let renderListCallback = null; let renderPreviewCallback = null; let getCurrentWorksheetBasenameCallback = null; /** * Initialisiert das Learning Units Modul * @param {Object} options - Konfiguration */ export function initLearningUnitsModule(options = {}) { // DOM-Referenzen unitListEl = document.getElementById('unit-list') || options.unitListEl; unitHeading1 = document.getElementById('unit-heading-1') || options.unitHeading1; unitHeading2 = document.getElementById('unit-heading-2') || options.unitHeading2; btnAddUnit = document.getElementById('btn-add-unit') || options.btnAddUnit; btnToggleFilter = document.getElementById('btn-toggle-filter') || options.btnToggleFilter; btnAttachCurrentToLu = document.getElementById('btn-attach-current-to-lu') || options.btnAttachCurrentToLu; // Form Inputs unitStudentInput = document.getElementById('unit-student') || options.unitStudentInput; unitSubjectInput = document.getElementById('unit-subject') || options.unitSubjectInput; unitGradeInput = document.getElementById('unit-grade') || options.unitGradeInput; unitTitleInput = document.getElementById('unit-title') || options.unitTitleInput; // Callbacks getEingangFilesCallback = options.getEingangFiles || (() => []); getAllEingangFilesCallback = options.getAllEingangFiles || (() => []); getAllWorksheetPairsCallback = options.getAllWorksheetPairs || (() => ({})); setFilteredDataCallback = options.setFilteredData || (() => {}); renderListCallback = options.renderList || (() => {}); renderPreviewCallback = options.renderPreview || (() => {}); getCurrentWorksheetBasenameCallback = options.getCurrentWorksheetBasename || (() => null); // Event-Listener if (btnAddUnit) { btnAddUnit.addEventListener('click', (ev) => { ev.preventDefault(); addUnitFromForm(); }); } if (btnAttachCurrentToLu) { btnAttachCurrentToLu.addEventListener('click', (ev) => { ev.preventDefault(); attachCurrentWorksheetToUnit(); }); } if (btnToggleFilter) { btnToggleFilter.addEventListener('click', () => { showOnlyUnitFiles = !showOnlyUnitFiles; if (showOnlyUnitFiles) { btnToggleFilter.textContent = t('only_unit') || 'Nur Lerneinheit'; btnToggleFilter.classList.add('btn-primary'); } else { btnToggleFilter.textContent = t('all_files') || 'Alle Dateien'; btnToggleFilter.classList.remove('btn-primary'); } applyUnitFilter(); }); } // Initial laden loadLearningUnits(); } /** * Aktualisiert die Überschrift mit dem Namen der aktuellen Lerneinheit * @param {Object|null} unit - Lerneinheit oder null */ function updateUnitHeading(unit = null) { if (!unit && currentUnitId && units && units.length) { unit = units.find((u) => u.id === currentUnitId) || null; } let text = t('no_unit_selected') || 'Keine Lerneinheit ausgewählt'; if (unit) { const name = unit.label || unit.title || t('learning_unit') || 'Lerneinheit'; text = (t('learning_unit') || 'Lerneinheit') + ': ' + name; } if (unitHeading1) unitHeading1.textContent = text; if (unitHeading2) unitHeading2.textContent = text; } /** * Wendet den Lerneinheiten-Filter an * Zeigt nur Dateien der aktuellen Lerneinheit oder alle Dateien */ export function applyUnitFilter() { let unit = null; if (currentUnitId && units && units.length) { unit = units.find((u) => u.id === currentUnitId) || null; } const allEingangFiles = getAllEingangFilesCallback(); const allWorksheetPairs = getAllWorksheetPairsCallback(); // Wenn Filter deaktiviert ODER keine Lerneinheit ausgewählt -> alle Dateien anzeigen if (!showOnlyUnitFiles || !unit || !Array.isArray(unit.worksheet_files) || unit.worksheet_files.length === 0) { setFilteredDataCallback(allEingangFiles.slice(), { ...allWorksheetPairs }, 0); renderListCallback(); renderPreviewCallback(); updateUnitHeading(unit); return; } // Filter aktiv: nur Dateien der aktuellen Lerneinheit anzeigen const allowed = new Set(unit.worksheet_files || []); const filteredFiles = allEingangFiles.filter((f) => allowed.has(f)); const filteredPairs = {}; Object.keys(allWorksheetPairs).forEach((key) => { if (allowed.has(key)) { filteredPairs[key] = allWorksheetPairs[key]; } }); setFilteredDataCallback(filteredFiles, filteredPairs, 0); renderListCallback(); renderPreviewCallback(); updateUnitHeading(unit); } /** * Lädt alle Lerneinheiten vom Server */ export async function loadLearningUnits() { try { const resp = await fetch('/api/learning-units/'); if (!resp.ok) { console.error('Fehler beim Laden der Lerneinheiten', resp.status); return; } units = await resp.json(); if (units.length && !currentUnitId) { currentUnitId = units[0].id; } renderUnits(); applyUnitFilter(); } catch (e) { console.error('Netzwerkfehler beim Laden der Lerneinheiten', e); } } /** * Rendert die Lerneinheiten-Liste */ export function renderUnits() { if (!unitListEl) return; unitListEl.innerHTML = ''; if (!units.length) { const li = document.createElement('li'); li.className = 'unit-item'; li.textContent = t('no_units_yet') || 'Noch keine Lerneinheiten angelegt.'; unitListEl.appendChild(li); updateUnitHeading(null); return; } units.forEach((u) => { const li = document.createElement('li'); li.className = 'unit-item'; if (u.id === currentUnitId) { li.classList.add('active'); } const contentDiv = document.createElement('div'); contentDiv.style.flex = '1'; contentDiv.style.minWidth = '0'; const titleEl = document.createElement('div'); titleEl.textContent = u.label || u.title || t('learning_unit') || 'Lerneinheit'; const metaEl = document.createElement('div'); metaEl.className = 'unit-item-meta'; const metaParts = []; if (u.meta) { metaParts.push(u.meta); } if (Array.isArray(u.worksheet_files)) { metaParts.push((t('worksheets') || 'Blätter') + ': ' + u.worksheet_files.length); } metaEl.textContent = metaParts.join(' · '); contentDiv.appendChild(titleEl); contentDiv.appendChild(metaEl); // Delete-Button const deleteBtn = document.createElement('span'); deleteBtn.textContent = '🗑️'; deleteBtn.style.cursor = 'pointer'; deleteBtn.style.fontSize = '12px'; deleteBtn.style.color = '#ef4444'; deleteBtn.title = t('delete_unit') || 'Lerneinheit löschen'; deleteBtn.addEventListener('click', async (ev) => { ev.stopPropagation(); const confirmMsg = t('confirm_delete_unit') || 'Lerneinheit "{name}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'; const ok = confirm(confirmMsg.replace('{name}', u.label || u.title || '')); if (!ok) return; await deleteLearningUnit(u.id); }); li.appendChild(contentDiv); li.appendChild(deleteBtn); li.addEventListener('click', () => { currentUnitId = u.id; renderUnits(); applyUnitFilter(); // Event für andere Module window.dispatchEvent(new CustomEvent('unitSelected', { detail: { unitId: u.id, unit: u } })); }); unitListEl.appendChild(li); }); } /** * Erstellt eine neue Lerneinheit aus dem Formular */ export async function addUnitFromForm() { const student = (unitStudentInput && unitStudentInput.value || '').trim(); const subject = (unitSubjectInput && unitSubjectInput.value || '').trim(); const grade = (unitGradeInput && unitGradeInput.value || '').trim(); const title = (unitTitleInput && unitTitleInput.value || '').trim(); if (!student && !subject && !title) { alert(t('unit_form_empty') || 'Bitte mindestens einen Wert (Schüler/in, Fach oder Thema) eintragen.'); return; } const payload = { student, subject, title, grade, }; try { const resp = await fetch('/api/learning-units/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!resp.ok) { console.error('Fehler beim Anlegen der Lerneinheit', resp.status); alert(t('unit_create_error') || 'Lerneinheit konnte nicht angelegt werden.'); return; } const created = await resp.json(); units.push(created); currentUnitId = created.id; // Formular leeren if (unitStudentInput) unitStudentInput.value = ''; if (unitSubjectInput) unitSubjectInput.value = ''; if (unitTitleInput) unitTitleInput.value = ''; if (unitGradeInput) unitGradeInput.value = ''; renderUnits(); applyUnitFilter(); // Event für andere Module window.dispatchEvent(new CustomEvent('unitCreated', { detail: { unit: created } })); } catch (e) { console.error('Netzwerkfehler beim Anlegen der Lerneinheit', e); alert(t('network_error') || 'Netzwerkfehler beim Anlegen der Lerneinheit.'); } } /** * Ordnet das aktuelle Arbeitsblatt der aktuellen Lerneinheit zu */ export async function attachCurrentWorksheetToUnit() { if (!currentUnitId) { alert(t('select_unit_first') || 'Bitte zuerst eine Lerneinheit auswählen oder anlegen.'); return; } const basename = getCurrentWorksheetBasenameCallback(); if (!basename) { alert(t('select_worksheet_first') || 'Bitte zuerst ein Arbeitsblatt im linken Bereich auswählen.'); return; } const payload = { worksheet_files: [basename] }; try { const resp = await fetch(`/api/learning-units/${currentUnitId}/attach-worksheets`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!resp.ok) { console.error('Fehler beim Zuordnen des Arbeitsblatts', resp.status); alert(t('attach_error') || 'Arbeitsblatt konnte nicht zugeordnet werden.'); return; } const updated = await resp.json(); const idx = units.findIndex((u) => u.id === updated.id); if (idx !== -1) { units[idx] = updated; } renderUnits(); applyUnitFilter(); // Event für andere Module window.dispatchEvent(new CustomEvent('worksheetAttached', { detail: { unitId: currentUnitId, filename: basename } })); } catch (e) { console.error('Netzwerkfehler beim Zuordnen des Arbeitsblatts', e); alert(t('network_error') || 'Netzwerkfehler beim Zuordnen des Arbeitsblatts.'); } } /** * Entfernt ein Arbeitsblatt aus der aktuellen Lerneinheit * @param {string} filename - Dateiname des Arbeitsblatts */ export async function removeWorksheetFromCurrentUnit(filename) { if (!currentUnitId) { alert(t('select_unit_first') || 'Bitte zuerst eine Lerneinheit auswählen.'); return; } if (!filename) { alert(t('error_no_filename') || 'Fehler: kein Dateiname übergeben.'); return; } const payload = { worksheet_file: filename }; try { const resp = await fetch(`/api/learning-units/${currentUnitId}/remove-worksheet`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!resp.ok) { console.error('Fehler beim Entfernen des Arbeitsblatts', resp.status); alert(t('remove_worksheet_error') || 'Arbeitsblatt konnte nicht aus der Lerneinheit entfernt werden.'); return; } const updated = await resp.json(); const idx = units.findIndex((u) => u.id === updated.id); if (idx !== -1) { units[idx] = updated; } renderUnits(); applyUnitFilter(); // Event für andere Module window.dispatchEvent(new CustomEvent('worksheetRemoved', { detail: { unitId: currentUnitId, filename } })); } catch (e) { console.error('Netzwerkfehler beim Entfernen des Arbeitsblatts', e); alert(t('network_error') || 'Netzwerkfehler beim Entfernen des Arbeitsblatts.'); } } /** * Löscht eine Lerneinheit * @param {string} unitId - ID der Lerneinheit */ export async function deleteLearningUnit(unitId) { if (!unitId) { alert(t('error_no_unit_id') || 'Fehler: keine Lerneinheit-ID übergeben.'); return; } try { setStatus(t('deleting_unit') || 'Lösche Lerneinheit …', '', 'busy'); const resp = await fetch(`/api/learning-units/${unitId}`, { method: 'DELETE', }); if (!resp.ok) { console.error('Fehler beim Löschen der Lerneinheit', resp.status); setStatus(t('delete_error') || 'Fehler beim Löschen', '', 'error'); alert(t('unit_delete_error') || 'Lerneinheit konnte nicht gelöscht werden.'); return; } const result = await resp.json(); if (result.status === 'deleted') { setStatus(t('unit_deleted') || 'Lerneinheit gelöscht', ''); // Lerneinheit aus der lokalen Liste entfernen units = units.filter((u) => u.id !== unitId); // Wenn die gelöschte Einheit ausgewählt war, Auswahl zurücksetzen if (currentUnitId === unitId) { currentUnitId = units.length > 0 ? units[0].id : null; } renderUnits(); applyUnitFilter(); // Event für andere Module window.dispatchEvent(new CustomEvent('unitDeleted', { detail: { unitId } })); } else { setStatus(t('error') || 'Fehler', t('unknown_error') || 'Unbekannter Fehler', 'error'); alert(t('unit_delete_error') || 'Fehler beim Löschen der Lerneinheit.'); } } catch (e) { console.error('Netzwerkfehler beim Löschen der Lerneinheit', e); setStatus(t('network_error') || 'Netzwerkfehler', String(e), 'error'); alert(t('network_error') || 'Netzwerkfehler beim Löschen der Lerneinheit.'); } } // === Getter und Setter === /** * Gibt alle Lerneinheiten zurück * @returns {Array} */ export function getUnits() { return units; } /** * Gibt die aktuelle Lerneinheit-ID zurück * @returns {string|null} */ export function getCurrentUnitId() { return currentUnitId; } /** * Setzt die aktuelle Lerneinheit-ID * @param {string|null} unitId */ export function setCurrentUnitId(unitId) { currentUnitId = unitId; renderUnits(); applyUnitFilter(); } /** * Gibt zurück, ob der Filter aktiv ist * @returns {boolean} */ export function getShowOnlyUnitFiles() { return showOnlyUnitFiles; } /** * Setzt den Filter-Status * @param {boolean} value */ export function setShowOnlyUnitFiles(value) { showOnlyUnitFiles = value; if (btnToggleFilter) { if (showOnlyUnitFiles) { btnToggleFilter.textContent = t('only_unit') || 'Nur Lerneinheit'; btnToggleFilter.classList.add('btn-primary'); } else { btnToggleFilter.textContent = t('all_files') || 'Alle Dateien'; btnToggleFilter.classList.remove('btn-primary'); } } applyUnitFilter(); } /** * Gibt die aktuelle Lerneinheit zurück * @returns {Object|null} */ export function getCurrentUnit() { if (!currentUnitId || !units.length) return null; return units.find((u) => u.id === currentUnitId) || null; }