This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/static/js/modules/learning-units-module.js
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

518 lines
16 KiB
JavaScript

/**
* 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;
}