"""
BreakPilot Studio - Abitur Docs Admin Module
Admin-Modul fuer die Verwaltung von Abitur-Dokumenten (NiBiS Niedersachsen).
Nur fuer Entwickler/Admins - nicht fuer normale Lehrer.
Features:
- ZIP-Import fuer Abitur-Dokumente
- Automatische Dateierkennung (Jahr, Fach, Niveau, Typ)
- Metadaten-Korrektur und Bestaetigung
- Indexierung fuer RAG-System
- Uebersicht aller Dokumente mit Filter
"""
class AbiturDocsAdminModule:
"""Admin-Modul fuer Abitur-Dokumentenverwaltung."""
@staticmethod
def get_css() -> str:
"""CSS fuer das Abitur Docs Admin-Modul."""
return """
/* =============================================
ABITUR DOCS ADMIN MODULE
============================================= */
.panel-abitur-docs-admin {
display: none;
flex-direction: column;
height: 100%;
background: var(--bp-bg);
}
/* Header */
.abitur-docs-header {
padding: 24px 32px;
background: var(--bp-surface);
border-bottom: 1px solid var(--bp-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.abitur-docs-header h1 {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
display: flex;
align-items: center;
gap: 12px;
}
.abitur-docs-header-badge {
font-size: 11px;
padding: 4px 10px;
border-radius: 12px;
background: rgba(139, 92, 246, 0.15);
color: #8b5cf6;
font-weight: 600;
}
.abitur-docs-actions {
display: flex;
gap: 12px;
}
/* Content Area - 2 Spalten */
.abitur-docs-content {
display: grid;
grid-template-columns: 1fr 400px;
gap: 24px;
padding: 24px 32px;
flex: 1;
overflow: hidden;
}
/* Dokumenten-Liste (links) */
.abitur-docs-list-section {
display: flex;
flex-direction: column;
gap: 16px;
overflow: hidden;
}
/* Filter-Bar */
.abitur-docs-filters {
display: flex;
gap: 12px;
flex-wrap: wrap;
padding: 16px;
background: var(--bp-surface);
border-radius: 12px;
border: 1px solid var(--bp-border);
}
.abitur-docs-filter-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.abitur-docs-filter-label {
font-size: 11px;
color: var(--bp-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.abitur-docs-filter-select {
background: var(--bp-surface-elevated);
border: 1px solid var(--bp-border);
border-radius: 6px;
padding: 8px 12px;
color: var(--bp-text);
font-size: 13px;
min-width: 140px;
}
.abitur-docs-search {
flex: 1;
min-width: 200px;
}
.abitur-docs-search input {
width: 100%;
background: var(--bp-surface-elevated);
border: 1px solid var(--bp-border);
border-radius: 6px;
padding: 8px 12px;
color: var(--bp-text);
font-size: 13px;
}
.abitur-docs-search input::placeholder {
color: var(--bp-text-muted);
}
/* Dokumenten-Tabelle */
.abitur-docs-table-container {
flex: 1;
overflow-y: auto;
background: var(--bp-surface);
border-radius: 12px;
border: 1px solid var(--bp-border);
}
.abitur-docs-table {
width: 100%;
border-collapse: collapse;
}
.abitur-docs-table th,
.abitur-docs-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid var(--bp-border);
}
.abitur-docs-table th {
background: var(--bp-surface-elevated);
font-size: 11px;
font-weight: 600;
color: var(--bp-text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
position: sticky;
top: 0;
}
.abitur-docs-table tr:hover {
background: var(--bp-surface-elevated);
}
.abitur-docs-table tr.selected {
background: rgba(139, 92, 246, 0.1);
}
.abitur-docs-filename {
font-family: monospace;
font-size: 12px;
color: var(--bp-text);
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.abitur-docs-status {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 11px;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
}
.abitur-docs-status.pending {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
.abitur-docs-status.recognized {
background: rgba(59, 130, 246, 0.15);
color: #3b82f6;
}
.abitur-docs-status.confirmed {
background: rgba(16, 185, 129, 0.15);
color: #10b981;
}
.abitur-docs-status.indexed {
background: rgba(139, 92, 246, 0.15);
color: #8b5cf6;
}
.abitur-docs-status.error {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
/* Detail-Panel (rechts) */
.abitur-docs-detail-section {
display: flex;
flex-direction: column;
gap: 16px;
overflow-y: auto;
}
.abitur-docs-detail-card {
background: var(--bp-surface);
border-radius: 12px;
border: 1px solid var(--bp-border);
overflow: hidden;
}
.abitur-docs-detail-header {
padding: 16px;
background: var(--bp-surface-elevated);
border-bottom: 1px solid var(--bp-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.abitur-docs-detail-title {
font-size: 14px;
font-weight: 600;
color: var(--bp-text);
}
.abitur-docs-detail-body {
padding: 16px;
}
/* Metadaten-Form */
.abitur-docs-form-group {
margin-bottom: 16px;
}
.abitur-docs-form-label {
display: block;
font-size: 12px;
font-weight: 500;
color: var(--bp-text-muted);
margin-bottom: 6px;
}
.abitur-docs-form-input,
.abitur-docs-form-select {
width: 100%;
background: var(--bp-surface-elevated);
border: 1px solid var(--bp-border);
border-radius: 6px;
padding: 10px 12px;
color: var(--bp-text);
font-size: 13px;
}
.abitur-docs-form-input:focus,
.abitur-docs-form-select:focus {
outline: none;
border-color: var(--bp-primary);
}
.abitur-docs-form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
/* Erkennungs-Ergebnis */
.abitur-docs-recognition {
padding: 12px;
background: rgba(59, 130, 246, 0.1);
border-radius: 8px;
margin-bottom: 16px;
}
.abitur-docs-recognition-title {
font-size: 11px;
font-weight: 600;
color: #3b82f6;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.abitur-docs-recognition-item {
display: flex;
justify-content: space-between;
font-size: 12px;
color: var(--bp-text);
padding: 4px 0;
}
.abitur-docs-recognition-key {
color: var(--bp-text-muted);
}
.abitur-docs-recognition-value {
font-weight: 500;
}
.abitur-docs-recognition-confidence {
font-size: 10px;
color: var(--bp-text-muted);
margin-left: 8px;
}
/* Upload-Bereich */
.abitur-docs-upload-card {
background: var(--bp-surface);
border-radius: 12px;
border: 2px dashed var(--bp-border);
padding: 32px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.abitur-docs-upload-card:hover {
border-color: var(--bp-primary);
background: var(--bp-surface-elevated);
}
.abitur-docs-upload-card.dragover {
border-color: var(--bp-primary);
background: rgba(108, 27, 27, 0.1);
}
.abitur-docs-upload-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.6;
}
.abitur-docs-upload-title {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 8px;
}
.abitur-docs-upload-hint {
font-size: 13px;
color: var(--bp-text-muted);
}
/* Statistiken */
.abitur-docs-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.abitur-docs-stat {
background: var(--bp-surface);
border-radius: 8px;
border: 1px solid var(--bp-border);
padding: 16px;
text-align: center;
}
.abitur-docs-stat-value {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
}
.abitur-docs-stat-label {
font-size: 11px;
color: var(--bp-text-muted);
margin-top: 4px;
}
/* Buttons im Detail-Panel */
.abitur-docs-btn-group {
display: flex;
gap: 8px;
margin-top: 16px;
}
.abitur-docs-btn {
flex: 1;
padding: 10px 16px;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.abitur-docs-btn-primary {
background: var(--bp-primary);
color: white;
}
.abitur-docs-btn-primary:hover {
background: var(--bp-primary-hover);
}
.abitur-docs-btn-secondary {
background: var(--bp-surface-elevated);
color: var(--bp-text);
border: 1px solid var(--bp-border);
}
.abitur-docs-btn-secondary:hover {
background: var(--bp-border);
}
.abitur-docs-btn-danger {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.abitur-docs-btn-danger:hover {
background: rgba(239, 68, 68, 0.25);
}
/* Empty State */
.abitur-docs-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px;
color: var(--bp-text-muted);
}
.abitur-docs-empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.abitur-docs-empty-text {
font-size: 14px;
}
/* Progress */
.abitur-docs-progress {
height: 4px;
background: var(--bp-surface-elevated);
border-radius: 2px;
overflow: hidden;
margin-top: 12px;
}
.abitur-docs-progress-bar {
height: 100%;
background: var(--bp-primary);
transition: width 0.3s;
}
/* Toast Notifications */
.abitur-docs-toast {
position: fixed;
bottom: 24px;
right: 24px;
padding: 16px 24px;
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
z-index: 1000;
display: flex;
align-items: center;
gap: 12px;
animation: slideInRight 0.3s ease;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.abitur-docs-toast.success {
border-left: 4px solid var(--bp-success);
}
.abitur-docs-toast.error {
border-left: 4px solid var(--bp-danger);
}
.abitur-docs-toast.info {
border-left: 4px solid var(--bp-info);
}
"""
@staticmethod
def get_html() -> str:
"""HTML fuer das Abitur Docs Admin-Modul."""
return """
Niveau
Status
| Dateiname |
Jahr |
Fach |
Niveau |
Typ |
Status |
📦
ZIP-Datei hierher ziehen
oder klicken zum Auswaehlen
"""
@staticmethod
def get_js() -> str:
"""JavaScript fuer das Abitur Docs Admin-Modul."""
return """
// =============================================
// ABITUR DOCS ADMIN MODULE
// =============================================
let abiturDocsInitialized = false;
let abiturDocsList = [];
let abiturDocsEnums = { faecher: [], bundeslaender: [], dokumenttypen: [], niveaus: [] };
let selectedAbiturDoc = null;
async function loadAbiturDocsAdminModule() {
if (abiturDocsInitialized) {
console.log('Abitur Docs Admin already initialized');
return;
}
console.log('Loading Abitur Docs Admin Module...');
// Enums laden
await abiturDocsLoadEnums();
// Dokumente laden
await abiturDocsRefresh();
abiturDocsInitialized = true;
console.log('Abitur Docs Admin Module loaded');
}
async function abiturDocsLoadEnums() {
try {
const response = await fetch('/api/abitur-docs/enums');
if (response.ok) {
abiturDocsEnums = await response.json();
abiturDocsPopulateFilterDropdowns();
}
} catch (e) {
console.error('Error loading enums:', e);
}
}
function abiturDocsPopulateFilterDropdowns() {
// Fach-Filter und Detail-Select fuellen
const fachFilter = document.getElementById('filter-fach');
const fachDetail = document.getElementById('detail-fach');
if (fachFilter && abiturDocsEnums.faecher) {
fachFilter.innerHTML = '';
abiturDocsEnums.faecher.forEach(f => {
fachFilter.innerHTML += ``;
});
}
if (fachDetail && abiturDocsEnums.faecher) {
fachDetail.innerHTML = '';
abiturDocsEnums.faecher.forEach(f => {
fachDetail.innerHTML += ``;
});
}
}
async function abiturDocsRefresh() {
try {
// Filters sammeln
const params = new URLSearchParams();
const jahr = document.getElementById('filter-jahr')?.value;
const fach = document.getElementById('filter-fach')?.value;
const niveau = document.getElementById('filter-niveau')?.value;
const status = document.getElementById('filter-status')?.value;
const search = document.getElementById('filter-search')?.value;
if (jahr) params.append('jahr', jahr);
if (fach) params.append('fach', fach);
if (niveau) params.append('niveau', niveau);
if (status) params.append('status', status);
if (search) params.append('search', search);
const response = await fetch('/api/abitur-docs/documents?' + params.toString());
if (response.ok) {
const data = await response.json();
abiturDocsList = data.documents || [];
abiturDocsRenderTable();
abiturDocsUpdateStats();
}
} catch (e) {
console.error('Error loading documents:', e);
abiturDocsShowToast('Fehler beim Laden der Dokumente', 'error');
}
}
function abiturDocsApplyFilters() {
abiturDocsRefresh();
}
function abiturDocsRenderTable() {
const tbody = document.getElementById('abitur-docs-table-body');
if (!tbody) return;
if (abiturDocsList.length === 0) {
tbody.innerHTML = `
|
Keine Dokumente gefunden
|
`;
return;
}
tbody.innerHTML = abiturDocsList.map(doc => `
| ${doc.filename} |
${doc.metadata?.jahr || '-'} |
${doc.metadata?.fach || '-'} |
${doc.metadata?.niveau || '-'} |
${doc.metadata?.dokument_typ || '-'} |
${abiturDocsStatusLabel(doc.status)} |
`).join('');
}
function abiturDocsStatusLabel(status) {
const labels = {
'pending': 'Ausstehend',
'recognized': 'Erkannt',
'confirmed': 'Bestaetigt',
'indexed': 'Indexiert',
'error': 'Fehler'
};
return labels[status] || status;
}
function abiturDocsUpdateStats() {
// Zaehlen nach Status
const stats = {
total: abiturDocsList.length,
pending: abiturDocsList.filter(d => d.status === 'pending').length,
confirmed: abiturDocsList.filter(d => d.status === 'confirmed').length,
indexed: abiturDocsList.filter(d => d.status === 'indexed').length
};
document.getElementById('stat-total').textContent = stats.total;
document.getElementById('stat-pending').textContent = stats.pending;
document.getElementById('stat-confirmed').textContent = stats.confirmed;
document.getElementById('stat-indexed').textContent = stats.indexed;
}
async function abiturDocsSelectDocument(docId) {
const doc = abiturDocsList.find(d => d.id === docId);
if (!doc) return;
selectedAbiturDoc = doc;
abiturDocsRenderTable(); // Re-render to show selection
// Detail-Panel zeigen
document.getElementById('abitur-docs-upload-area').style.display = 'none';
document.getElementById('abitur-docs-detail-card').style.display = 'block';
document.getElementById('abitur-docs-actions-card').style.display = 'block';
// Formular fuellen
document.getElementById('detail-id').value = doc.id;
document.getElementById('detail-filename').value = doc.filename;
document.getElementById('detail-jahr').value = doc.metadata?.jahr || '';
document.getElementById('detail-bundesland').value = doc.metadata?.bundesland || 'niedersachsen';
document.getElementById('detail-fach').value = doc.metadata?.fach || '';
document.getElementById('detail-niveau').value = doc.metadata?.niveau || '';
document.getElementById('detail-doktyp').value = doc.metadata?.dokument_typ || '';
document.getElementById('detail-aufgabe-nr').value = doc.metadata?.aufgaben_nummer || '';
// Status-Badge
const statusEl = document.getElementById('detail-status');
statusEl.textContent = abiturDocsStatusLabel(doc.status);
statusEl.className = 'abitur-docs-status ' + doc.status;
// Erkennungs-Ergebnis anzeigen
const recItems = document.getElementById('detail-recognition-items');
if (doc.recognition_result) {
const rec = doc.recognition_result;
recItems.innerHTML = `
Confidence:
${(rec.confidence * 100).toFixed(0)}%
${rec.extracted.jahr ? `
Jahr:
${rec.extracted.jahr}
` : ''}
${rec.extracted.fach ? `
Fach:
${rec.extracted.fach}
` : ''}
${rec.extracted.niveau ? `
Niveau:
${rec.extracted.niveau}
` : ''}
${rec.extracted.typ ? `
Typ:
${rec.extracted.typ}
` : ''}
`;
document.getElementById('detail-recognition').style.display = 'block';
} else {
document.getElementById('detail-recognition').style.display = 'none';
}
}
function abiturDocsClearSelection() {
selectedAbiturDoc = null;
abiturDocsRenderTable();
document.getElementById('abitur-docs-upload-area').style.display = 'block';
document.getElementById('abitur-docs-detail-card').style.display = 'none';
document.getElementById('abitur-docs-actions-card').style.display = 'none';
}
async function abiturDocsConfirmMetadata() {
if (!selectedAbiturDoc) return;
const metadata = {
jahr: parseInt(document.getElementById('detail-jahr').value) || null,
bundesland: document.getElementById('detail-bundesland').value || null,
fach: document.getElementById('detail-fach').value || null,
niveau: document.getElementById('detail-niveau').value || null,
dokument_typ: document.getElementById('detail-doktyp').value || null,
aufgaben_nummer: document.getElementById('detail-aufgabe-nr').value || null
};
try {
const response = await fetch(`/api/abitur-docs/documents/${selectedAbiturDoc.id}/metadata`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metadata)
});
if (response.ok) {
abiturDocsShowToast('Metadaten bestaetigt', 'success');
await abiturDocsRefresh();
// Re-select to update detail view
if (selectedAbiturDoc) {
abiturDocsSelectDocument(selectedAbiturDoc.id);
}
} else {
const err = await response.json();
abiturDocsShowToast(err.detail || 'Fehler beim Speichern', 'error');
}
} catch (e) {
console.error('Error updating metadata:', e);
abiturDocsShowToast('Fehler beim Speichern', 'error');
}
}
async function abiturDocsIndexDocument() {
if (!selectedAbiturDoc) return;
try {
const response = await fetch(`/api/abitur-docs/documents/${selectedAbiturDoc.id}/index`, {
method: 'POST'
});
if (response.ok) {
abiturDocsShowToast('Dokument indexiert', 'success');
await abiturDocsRefresh();
if (selectedAbiturDoc) {
abiturDocsSelectDocument(selectedAbiturDoc.id);
}
} else {
const err = await response.json();
abiturDocsShowToast(err.detail || 'Fehler beim Indexieren', 'error');
}
} catch (e) {
console.error('Error indexing:', e);
abiturDocsShowToast('Fehler beim Indexieren', 'error');
}
}
function abiturDocsPreviewDocument() {
if (!selectedAbiturDoc?.file_path) return;
window.open('/api/abitur-docs/documents/' + selectedAbiturDoc.id + '/preview', '_blank');
}
async function abiturDocsDeleteDocument() {
if (!selectedAbiturDoc) return;
if (!confirm('Dokument wirklich loeschen?')) return;
try {
const response = await fetch(`/api/abitur-docs/documents/${selectedAbiturDoc.id}`, {
method: 'DELETE'
});
if (response.ok) {
abiturDocsShowToast('Dokument geloescht', 'success');
abiturDocsClearSelection();
await abiturDocsRefresh();
} else {
abiturDocsShowToast('Fehler beim Loeschen', 'error');
}
} catch (e) {
console.error('Error deleting:', e);
abiturDocsShowToast('Fehler beim Loeschen', 'error');
}
}
// Drag & Drop
function abiturDocsDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('dragover');
}
function abiturDocsDragLeave(e) {
e.currentTarget.classList.remove('dragover');
}
function abiturDocsHandleDrop(e) {
e.preventDefault();
e.currentTarget.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
abiturDocsUploadFile(files[0]);
}
}
function abiturDocsHandleFileSelect(e) {
const files = e.target.files;
if (files.length > 0) {
abiturDocsUploadFile(files[0]);
}
}
function abiturDocsShowUpload() {
document.getElementById('abitur-docs-file-input').click();
}
async function abiturDocsUploadFile(file) {
if (!file.name.endsWith('.zip')) {
abiturDocsShowToast('Bitte eine ZIP-Datei waehlen', 'error');
return;
}
abiturDocsShowToast('Import gestartet...', 'info');
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/abitur-docs/import-zip', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
abiturDocsShowToast(`${result.imported_count} Dokumente importiert`, 'success');
await abiturDocsRefresh();
} else {
const err = await response.json();
abiturDocsShowToast(err.detail || 'Fehler beim Import', 'error');
}
} catch (e) {
console.error('Error uploading:', e);
abiturDocsShowToast('Fehler beim Upload', 'error');
}
}
// Toast Helper
function abiturDocsShowToast(message, type = 'info') {
const existing = document.querySelector('.abitur-docs-toast');
if (existing) existing.remove();
const toast = document.createElement('div');
toast.className = `abitur-docs-toast ${type}`;
toast.innerHTML = `
${type === 'success' ? '✓' : type === 'error' ? '✗' : 'ℹ'}
${message}
`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
// Panel Show Function
function showAbiturDocsAdminPanel() {
console.log('showAbiturDocsAdminPanel called');
hideAllPanels();
const panel = document.getElementById('panel-abitur-docs-admin');
if (panel) {
panel.style.display = 'flex';
loadAbiturDocsAdminModule();
console.log('Abitur Docs Admin panel shown');
}
}
"""