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>
This commit is contained in:
889
backend/frontend/modules/lehrer_dashboard.py
Normal file
889
backend/frontend/modules/lehrer_dashboard.py
Normal file
@@ -0,0 +1,889 @@
|
||||
"""
|
||||
Lehrer-Dashboard Modul fuer das BreakPilot Studio.
|
||||
|
||||
Ein frei konfigurierbares Dashboard mit Drag & Drop Widget-System.
|
||||
Lehrer koennen ihre persoenliche Startseite aus verschiedenen Widgets zusammenstellen.
|
||||
"""
|
||||
|
||||
from .widgets import (
|
||||
TodosWidget,
|
||||
SchnellzugriffWidget,
|
||||
NotizenWidget,
|
||||
StundenplanWidget,
|
||||
KlassenWidget,
|
||||
FehlzeitenWidget,
|
||||
ArbeitenWidget,
|
||||
NachrichtenWidget,
|
||||
MatrixWidget,
|
||||
AlertsWidget,
|
||||
StatistikWidget,
|
||||
KalenderWidget,
|
||||
)
|
||||
|
||||
|
||||
class LehrerDashboardModule:
|
||||
"""
|
||||
Haupt-Modul fuer das konfigurierbare Lehrer-Dashboard.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_css() -> str:
|
||||
# Sammle CSS von allen Widgets
|
||||
widget_css = "\n".join([
|
||||
TodosWidget.get_css(),
|
||||
SchnellzugriffWidget.get_css(),
|
||||
NotizenWidget.get_css(),
|
||||
StundenplanWidget.get_css(),
|
||||
KlassenWidget.get_css(),
|
||||
FehlzeitenWidget.get_css(),
|
||||
ArbeitenWidget.get_css(),
|
||||
NachrichtenWidget.get_css(),
|
||||
MatrixWidget.get_css(),
|
||||
AlertsWidget.get_css(),
|
||||
StatistikWidget.get_css(),
|
||||
KalenderWidget.get_css(),
|
||||
])
|
||||
|
||||
return f"""
|
||||
/* ===== Lehrer-Dashboard Styles ===== */
|
||||
.lehrer-dashboard-container {{
|
||||
padding: 24px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}}
|
||||
|
||||
/* Dashboard Header */
|
||||
.dashboard-header {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24px;
|
||||
}}
|
||||
|
||||
.dashboard-greeting {{
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--bp-text, #e5e7eb);
|
||||
margin-bottom: 4px;
|
||||
}}
|
||||
|
||||
.dashboard-date {{
|
||||
font-size: 14px;
|
||||
color: var(--bp-text-muted, #9ca3af);
|
||||
}}
|
||||
|
||||
.dashboard-actions {{
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}}
|
||||
|
||||
.dashboard-edit-btn {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
background: var(--bp-surface, #1e293b);
|
||||
border: 1px solid var(--bp-border, #475569);
|
||||
border-radius: 8px;
|
||||
color: var(--bp-text, #e5e7eb);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
|
||||
.dashboard-edit-btn:hover {{
|
||||
background: var(--bp-surface-elevated, #334155);
|
||||
border-color: var(--bp-primary, #6C1B1B);
|
||||
}}
|
||||
|
||||
.dashboard-edit-btn.active {{
|
||||
background: var(--bp-primary, #6C1B1B);
|
||||
border-color: var(--bp-primary, #6C1B1B);
|
||||
color: white;
|
||||
}}
|
||||
|
||||
/* Widget Grid */
|
||||
.dashboard-grid {{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}}
|
||||
|
||||
.dashboard-row {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}}
|
||||
|
||||
.dashboard-row.single {{
|
||||
grid-template-columns: 1fr;
|
||||
}}
|
||||
|
||||
/* Widget Container */
|
||||
.dashboard-widget {{
|
||||
position: relative;
|
||||
min-height: 200px;
|
||||
}}
|
||||
|
||||
.dashboard-widget.full {{
|
||||
grid-column: 1 / -1;
|
||||
}}
|
||||
|
||||
/* Widget Settings Button */
|
||||
.widget-settings-btn {{
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--bp-text-muted, #9ca3af);
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
}}
|
||||
|
||||
.widget-settings-btn:hover {{
|
||||
background: var(--bp-surface-elevated, #334155);
|
||||
color: var(--bp-text, #e5e7eb);
|
||||
}}
|
||||
|
||||
/* ===== Edit Mode Styles ===== */
|
||||
.dashboard-edit-mode .widget-catalog {{
|
||||
display: block !important;
|
||||
}}
|
||||
|
||||
.dashboard-edit-mode .dashboard-widget {{
|
||||
position: relative;
|
||||
}}
|
||||
|
||||
.dashboard-edit-mode .dashboard-widget::after {{
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 2px dashed var(--bp-border, #475569);
|
||||
border-radius: 12px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}}
|
||||
|
||||
.dashboard-edit-mode .dashboard-widget:hover::after {{
|
||||
opacity: 1;
|
||||
}}
|
||||
|
||||
.dashboard-edit-mode .widget-remove-btn {{
|
||||
display: flex !important;
|
||||
}}
|
||||
|
||||
/* Widget Remove Button */
|
||||
.widget-remove-btn {{
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(239, 68, 68, 0.9);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
|
||||
.widget-remove-btn:hover {{
|
||||
background: #ef4444;
|
||||
transform: scale(1.1);
|
||||
}}
|
||||
|
||||
/* Widget Catalog */
|
||||
.widget-catalog {{
|
||||
display: none;
|
||||
background: var(--bp-surface, #1e293b);
|
||||
border: 1px solid var(--bp-border, #475569);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 24px;
|
||||
}}
|
||||
|
||||
.widget-catalog-title {{
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--bp-text, #e5e7eb);
|
||||
margin-bottom: 12px;
|
||||
}}
|
||||
|
||||
.widget-catalog-grid {{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}}
|
||||
|
||||
.widget-catalog-item {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 14px;
|
||||
background: var(--bp-bg, #0f172a);
|
||||
border: 1px solid var(--bp-border-subtle, rgba(255,255,255,0.1));
|
||||
border-radius: 8px;
|
||||
cursor: grab;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}}
|
||||
|
||||
.widget-catalog-item:hover {{
|
||||
border-color: var(--bp-primary, #6C1B1B);
|
||||
transform: translateY(-2px);
|
||||
}}
|
||||
|
||||
.widget-catalog-item:active {{
|
||||
cursor: grabbing;
|
||||
}}
|
||||
|
||||
.widget-catalog-item.dragging {{
|
||||
opacity: 0.5;
|
||||
}}
|
||||
|
||||
.widget-catalog-item.disabled {{
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}}
|
||||
|
||||
.widget-catalog-item-icon {{
|
||||
font-size: 16px;
|
||||
}}
|
||||
|
||||
.widget-catalog-item-name {{
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--bp-text, #e5e7eb);
|
||||
}}
|
||||
|
||||
/* Drop Zone */
|
||||
.drop-zone {{
|
||||
display: none;
|
||||
min-height: 100px;
|
||||
border: 2px dashed var(--bp-border, #475569);
|
||||
border-radius: 12px;
|
||||
background: var(--bp-bg, #0f172a);
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
|
||||
.dashboard-edit-mode .drop-zone {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--bp-text-muted, #9ca3af);
|
||||
font-size: 13px;
|
||||
}}
|
||||
|
||||
.drop-zone.drag-over {{
|
||||
border-color: var(--bp-accent, #5ABF60);
|
||||
background: rgba(90, 191, 96, 0.1);
|
||||
color: var(--bp-accent, #5ABF60);
|
||||
}}
|
||||
|
||||
/* Add Row Button */
|
||||
.add-row-btn {{
|
||||
display: none;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
border: 2px dashed var(--bp-border, #475569);
|
||||
border-radius: 12px;
|
||||
color: var(--bp-text-muted, #9ca3af);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
|
||||
.dashboard-edit-mode .add-row-btn {{
|
||||
display: block;
|
||||
}}
|
||||
|
||||
.add-row-btn:hover {{
|
||||
border-color: var(--bp-accent, #5ABF60);
|
||||
color: var(--bp-accent, #5ABF60);
|
||||
}}
|
||||
|
||||
/* Widget Settings Modal */
|
||||
.widget-settings-modal {{
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}}
|
||||
|
||||
.widget-settings-modal.active {{
|
||||
display: flex;
|
||||
}}
|
||||
|
||||
.widget-settings-content {{
|
||||
background: var(--bp-surface, #1e293b);
|
||||
border: 1px solid var(--bp-border, #475569);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
}}
|
||||
|
||||
.widget-settings-title {{
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--bp-text, #e5e7eb);
|
||||
margin-bottom: 16px;
|
||||
}}
|
||||
|
||||
.widget-settings-close {{
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--bp-text-muted, #9ca3af);
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
}}
|
||||
|
||||
.widget-settings-close:hover {{
|
||||
background: var(--bp-surface-elevated, #334155);
|
||||
}}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {{
|
||||
.lehrer-dashboard-container {{
|
||||
padding: 16px;
|
||||
}}
|
||||
|
||||
.dashboard-header {{
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}}
|
||||
|
||||
.dashboard-row {{
|
||||
grid-template-columns: 1fr;
|
||||
}}
|
||||
|
||||
.dashboard-greeting {{
|
||||
font-size: 20px;
|
||||
}}
|
||||
|
||||
.widget-catalog-grid {{
|
||||
flex-direction: column;
|
||||
}}
|
||||
|
||||
.widget-catalog-item {{
|
||||
width: 100%;
|
||||
}}
|
||||
}}
|
||||
|
||||
/* Widget CSS */
|
||||
{widget_css}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_html() -> str:
|
||||
return """
|
||||
<div class="panel panel-lehrer-dashboard" id="panel-lehrer-dashboard" style="display: none;">
|
||||
<div class="lehrer-dashboard-container">
|
||||
<!-- Header -->
|
||||
<div class="dashboard-header">
|
||||
<div>
|
||||
<div class="dashboard-greeting" id="dashboard-greeting">Guten Tag!</div>
|
||||
<div class="dashboard-date" id="dashboard-date"></div>
|
||||
</div>
|
||||
<div class="dashboard-actions">
|
||||
<button class="dashboard-edit-btn" id="dashboard-edit-btn" onclick="toggleDashboardEditMode()">
|
||||
<span>🎨</span>
|
||||
<span id="edit-btn-text">Anpassen</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Widget Catalog (Edit Mode) -->
|
||||
<div class="widget-catalog" id="widget-catalog">
|
||||
<div class="widget-catalog-title">Widget-Katalog (ziehen Sie Widgets auf Ihr Dashboard):</div>
|
||||
<div class="widget-catalog-grid" id="widget-catalog-grid">
|
||||
<!-- Wird dynamisch gefuellt -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dashboard Grid -->
|
||||
<div class="dashboard-grid" id="dashboard-grid">
|
||||
<!-- Wird dynamisch gefuellt -->
|
||||
</div>
|
||||
|
||||
<!-- Add Row Button -->
|
||||
<button class="add-row-btn" onclick="addDashboardRow()">+ Neue Reihe hinzufuegen</button>
|
||||
</div>
|
||||
|
||||
<!-- Widget Settings Modal -->
|
||||
<div class="widget-settings-modal" id="widget-settings-modal">
|
||||
<div class="widget-settings-content">
|
||||
<button class="widget-settings-close" onclick="closeWidgetSettings()">×</button>
|
||||
<div class="widget-settings-title" id="widget-settings-title">Widget-Einstellungen</div>
|
||||
<div id="widget-settings-body">
|
||||
<!-- Wird dynamisch gefuellt -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_js() -> str:
|
||||
# Sammle JS von allen Widgets
|
||||
widget_js = "\n".join([
|
||||
TodosWidget.get_js(),
|
||||
SchnellzugriffWidget.get_js(),
|
||||
NotizenWidget.get_js(),
|
||||
StundenplanWidget.get_js(),
|
||||
KlassenWidget.get_js(),
|
||||
FehlzeitenWidget.get_js(),
|
||||
ArbeitenWidget.get_js(),
|
||||
NachrichtenWidget.get_js(),
|
||||
MatrixWidget.get_js(),
|
||||
AlertsWidget.get_js(),
|
||||
StatistikWidget.get_js(),
|
||||
KalenderWidget.get_js(),
|
||||
])
|
||||
|
||||
return f"""
|
||||
// ===== Lehrer-Dashboard JavaScript =====
|
||||
const DASHBOARD_LAYOUT_KEY = 'bp-dashboard-layout';
|
||||
const LEHRER_PROFIL_KEY = 'bp-lehrer-profil';
|
||||
let dashboardEditMode = false;
|
||||
let lehrerDashboardInitialized = false;
|
||||
|
||||
// Widget Registry
|
||||
const WidgetRegistry = {{
|
||||
stundenplan: {{
|
||||
id: 'stundenplan',
|
||||
name: 'Stundenplan',
|
||||
icon: '📅',
|
||||
color: '#3b82f6',
|
||||
defaultWidth: 'half',
|
||||
init: initStundenplanWidget,
|
||||
getHtml: () => `{StundenplanWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
klassen: {{
|
||||
id: 'klassen',
|
||||
name: 'Meine Klassen',
|
||||
icon: '📊',
|
||||
color: '#8b5cf6',
|
||||
defaultWidth: 'half',
|
||||
init: initKlassenWidget,
|
||||
getHtml: () => `{KlassenWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
fehlzeiten: {{
|
||||
id: 'fehlzeiten',
|
||||
name: 'Fehlzeiten',
|
||||
icon: '⚠',
|
||||
color: '#ef4444',
|
||||
defaultWidth: 'half',
|
||||
init: initFehlzeitenWidget,
|
||||
getHtml: () => `{FehlzeitenWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
arbeiten: {{
|
||||
id: 'arbeiten',
|
||||
name: 'Arbeiten',
|
||||
icon: '📝',
|
||||
color: '#f59e0b',
|
||||
defaultWidth: 'half',
|
||||
init: initArbeitenWidget,
|
||||
getHtml: () => `{ArbeitenWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
todos: {{
|
||||
id: 'todos',
|
||||
name: 'To-Dos',
|
||||
icon: '✓',
|
||||
color: '#10b981',
|
||||
defaultWidth: 'half',
|
||||
init: initTodosWidget,
|
||||
getHtml: () => `{TodosWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
nachrichten: {{
|
||||
id: 'nachrichten',
|
||||
name: 'E-Mails',
|
||||
icon: '📧',
|
||||
color: '#06b6d4',
|
||||
defaultWidth: 'half',
|
||||
init: initNachrichtenWidget,
|
||||
getHtml: () => `{NachrichtenWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
matrix: {{
|
||||
id: 'matrix',
|
||||
name: 'Matrix-Chat',
|
||||
icon: '💬',
|
||||
color: '#8b5cf6',
|
||||
defaultWidth: 'half',
|
||||
init: initMatrixWidget,
|
||||
getHtml: () => `{MatrixWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
alerts: {{
|
||||
id: 'alerts',
|
||||
name: 'Google Alerts',
|
||||
icon: '🔔',
|
||||
color: '#f59e0b',
|
||||
defaultWidth: 'half',
|
||||
init: initAlertsWidget,
|
||||
getHtml: () => `{AlertsWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
statistik: {{
|
||||
id: 'statistik',
|
||||
name: 'Statistik',
|
||||
icon: '📈',
|
||||
color: '#3b82f6',
|
||||
defaultWidth: 'full',
|
||||
init: initStatistikWidget,
|
||||
getHtml: () => `{StatistikWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
schnellzugriff: {{
|
||||
id: 'schnellzugriff',
|
||||
name: 'Schnellzugriff',
|
||||
icon: '⚡',
|
||||
color: '#6b7280',
|
||||
defaultWidth: 'full',
|
||||
init: initSchnellzugriffWidget,
|
||||
getHtml: () => `{SchnellzugriffWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
notizen: {{
|
||||
id: 'notizen',
|
||||
name: 'Notizen',
|
||||
icon: '📋',
|
||||
color: '#fbbf24',
|
||||
defaultWidth: 'half',
|
||||
init: initNotizenWidget,
|
||||
getHtml: () => `{NotizenWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}},
|
||||
kalender: {{
|
||||
id: 'kalender',
|
||||
name: 'Termine',
|
||||
icon: '📆',
|
||||
color: '#ec4899',
|
||||
defaultWidth: 'half',
|
||||
init: initKalenderWidget,
|
||||
getHtml: () => `{KalenderWidget.get_html().replace('`', '\\`').replace('${', '\\${')}`
|
||||
}}
|
||||
}};
|
||||
|
||||
// Default Layout
|
||||
function getDefaultLayout() {{
|
||||
return {{
|
||||
version: 1,
|
||||
rows: [
|
||||
{{
|
||||
id: 'row-1',
|
||||
widgets: [
|
||||
{{ widgetId: 'stundenplan', width: 'half' }},
|
||||
{{ widgetId: 'klassen', width: 'half' }}
|
||||
]
|
||||
}},
|
||||
{{
|
||||
id: 'row-2',
|
||||
widgets: [
|
||||
{{ widgetId: 'fehlzeiten', width: 'half' }},
|
||||
{{ widgetId: 'arbeiten', width: 'half' }}
|
||||
]
|
||||
}},
|
||||
{{
|
||||
id: 'row-3',
|
||||
widgets: [
|
||||
{{ widgetId: 'todos', width: 'half' }},
|
||||
{{ widgetId: 'nachrichten', width: 'half' }}
|
||||
]
|
||||
}},
|
||||
{{
|
||||
id: 'row-4',
|
||||
widgets: [
|
||||
{{ widgetId: 'schnellzugriff', width: 'full' }}
|
||||
]
|
||||
}}
|
||||
]
|
||||
}};
|
||||
}}
|
||||
|
||||
function loadDashboardLayout() {{
|
||||
const stored = localStorage.getItem(DASHBOARD_LAYOUT_KEY);
|
||||
return stored ? JSON.parse(stored) : getDefaultLayout();
|
||||
}}
|
||||
|
||||
function saveDashboardLayout(layout) {{
|
||||
localStorage.setItem(DASHBOARD_LAYOUT_KEY, JSON.stringify(layout));
|
||||
}}
|
||||
|
||||
function getGreeting() {{
|
||||
const hour = new Date().getHours();
|
||||
if (hour < 12) return 'Guten Morgen';
|
||||
if (hour < 18) return 'Guten Tag';
|
||||
return 'Guten Abend';
|
||||
}}
|
||||
|
||||
function getLehrerName() {{
|
||||
const profil = localStorage.getItem(LEHRER_PROFIL_KEY);
|
||||
if (profil) {{
|
||||
try {{
|
||||
return JSON.parse(profil).name || 'Lehrer';
|
||||
}} catch (e) {{}}
|
||||
}}
|
||||
return '';
|
||||
}}
|
||||
|
||||
function formatDashboardDate() {{
|
||||
return new Date().toLocaleDateString('de-DE', {{
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
}});
|
||||
}}
|
||||
|
||||
function renderDashboardHeader() {{
|
||||
const greetingEl = document.getElementById('dashboard-greeting');
|
||||
const dateEl = document.getElementById('dashboard-date');
|
||||
|
||||
if (greetingEl) {{
|
||||
const name = getLehrerName();
|
||||
greetingEl.textContent = `${{getGreeting()}}${{name ? ', ' + name : ''}}!`;
|
||||
}}
|
||||
|
||||
if (dateEl) {{
|
||||
dateEl.textContent = formatDashboardDate();
|
||||
}}
|
||||
}}
|
||||
|
||||
function renderWidgetCatalog() {{
|
||||
const grid = document.getElementById('widget-catalog-grid');
|
||||
if (!grid) return;
|
||||
|
||||
const layout = loadDashboardLayout();
|
||||
const usedWidgets = new Set();
|
||||
|
||||
layout.rows.forEach(row => {{
|
||||
row.widgets.forEach(w => usedWidgets.add(w.widgetId));
|
||||
}});
|
||||
|
||||
grid.innerHTML = Object.values(WidgetRegistry).map(widget => {{
|
||||
const isUsed = usedWidgets.has(widget.id);
|
||||
return `
|
||||
<div class="widget-catalog-item ${{isUsed ? 'disabled' : ''}}"
|
||||
draggable="${{!isUsed}}"
|
||||
data-widget-id="${{widget.id}}"
|
||||
ondragstart="handleWidgetDragStart(event)"
|
||||
ondragend="handleWidgetDragEnd(event)">
|
||||
<span class="widget-catalog-item-icon">${{widget.icon}}</span>
|
||||
<span class="widget-catalog-item-name">${{widget.name}}</span>
|
||||
</div>
|
||||
`;
|
||||
}}).join('');
|
||||
}}
|
||||
|
||||
function renderDashboardGrid() {{
|
||||
const grid = document.getElementById('dashboard-grid');
|
||||
if (!grid) return;
|
||||
|
||||
const layout = loadDashboardLayout();
|
||||
|
||||
grid.innerHTML = layout.rows.map((row, rowIndex) => {{
|
||||
const isSingleFull = row.widgets.length === 1 && row.widgets[0].width === 'full';
|
||||
|
||||
return `
|
||||
<div class="dashboard-row ${{isSingleFull ? 'single' : ''}}" data-row-id="${{row.id}}">
|
||||
${{row.widgets.map((w, widgetIndex) => {{
|
||||
const widget = WidgetRegistry[w.widgetId];
|
||||
if (!widget) return '';
|
||||
|
||||
return `
|
||||
<div class="dashboard-widget ${{w.width}}" data-widget-id="${{w.widgetId}}">
|
||||
<button class="widget-remove-btn" onclick="removeWidget('${{row.id}}', ${{widgetIndex}})">×</button>
|
||||
${{widget.getHtml()}}
|
||||
</div>
|
||||
`;
|
||||
}}).join('')}}
|
||||
${{dashboardEditMode && row.widgets.length < 2 ? `
|
||||
<div class="drop-zone"
|
||||
ondragover="handleDragOver(event)"
|
||||
ondragleave="handleDragLeave(event)"
|
||||
ondrop="handleDrop(event, '${{row.id}}')"
|
||||
data-row-id="${{row.id}}">
|
||||
Widget hier ablegen
|
||||
</div>
|
||||
` : ''}}
|
||||
</div>
|
||||
`;
|
||||
}}).join('');
|
||||
|
||||
// Initialize all widgets
|
||||
layout.rows.forEach(row => {{
|
||||
row.widgets.forEach(w => {{
|
||||
const widget = WidgetRegistry[w.widgetId];
|
||||
if (widget && widget.init) {{
|
||||
setTimeout(() => widget.init(), 0);
|
||||
}}
|
||||
}});
|
||||
}});
|
||||
}}
|
||||
|
||||
function toggleDashboardEditMode() {{
|
||||
dashboardEditMode = !dashboardEditMode;
|
||||
|
||||
const container = document.querySelector('.lehrer-dashboard-container');
|
||||
const btn = document.getElementById('dashboard-edit-btn');
|
||||
const btnText = document.getElementById('edit-btn-text');
|
||||
|
||||
if (container) {{
|
||||
if (dashboardEditMode) {{
|
||||
container.classList.add('dashboard-edit-mode');
|
||||
}} else {{
|
||||
container.classList.remove('dashboard-edit-mode');
|
||||
}}
|
||||
}}
|
||||
|
||||
if (btn) {{
|
||||
btn.classList.toggle('active', dashboardEditMode);
|
||||
}}
|
||||
|
||||
if (btnText) {{
|
||||
btnText.textContent = dashboardEditMode ? 'Fertig' : 'Anpassen';
|
||||
}}
|
||||
|
||||
renderWidgetCatalog();
|
||||
renderDashboardGrid();
|
||||
}}
|
||||
|
||||
function handleWidgetDragStart(event) {{
|
||||
const widgetId = event.target.dataset.widgetId;
|
||||
event.dataTransfer.setData('widget-id', widgetId);
|
||||
event.target.classList.add('dragging');
|
||||
}}
|
||||
|
||||
function handleWidgetDragEnd(event) {{
|
||||
event.target.classList.remove('dragging');
|
||||
}}
|
||||
|
||||
function handleDragOver(event) {{
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.add('drag-over');
|
||||
}}
|
||||
|
||||
function handleDragLeave(event) {{
|
||||
event.currentTarget.classList.remove('drag-over');
|
||||
}}
|
||||
|
||||
function handleDrop(event, rowId) {{
|
||||
event.preventDefault();
|
||||
event.currentTarget.classList.remove('drag-over');
|
||||
|
||||
const widgetId = event.dataTransfer.getData('widget-id');
|
||||
if (!widgetId || !WidgetRegistry[widgetId]) return;
|
||||
|
||||
const layout = loadDashboardLayout();
|
||||
const row = layout.rows.find(r => r.id === rowId);
|
||||
|
||||
if (row && row.widgets.length < 2) {{
|
||||
const widget = WidgetRegistry[widgetId];
|
||||
row.widgets.push({{
|
||||
widgetId: widgetId,
|
||||
width: widget.defaultWidth === 'full' ? 'full' : 'half'
|
||||
}});
|
||||
|
||||
saveDashboardLayout(layout);
|
||||
renderWidgetCatalog();
|
||||
renderDashboardGrid();
|
||||
}}
|
||||
}}
|
||||
|
||||
function removeWidget(rowId, widgetIndex) {{
|
||||
const layout = loadDashboardLayout();
|
||||
const rowIndex = layout.rows.findIndex(r => r.id === rowId);
|
||||
|
||||
if (rowIndex !== -1) {{
|
||||
layout.rows[rowIndex].widgets.splice(widgetIndex, 1);
|
||||
|
||||
// Remove empty rows
|
||||
if (layout.rows[rowIndex].widgets.length === 0) {{
|
||||
layout.rows.splice(rowIndex, 1);
|
||||
}}
|
||||
|
||||
saveDashboardLayout(layout);
|
||||
renderWidgetCatalog();
|
||||
renderDashboardGrid();
|
||||
}}
|
||||
}}
|
||||
|
||||
function addDashboardRow() {{
|
||||
const layout = loadDashboardLayout();
|
||||
const newRowId = 'row-' + Date.now();
|
||||
|
||||
layout.rows.push({{
|
||||
id: newRowId,
|
||||
widgets: []
|
||||
}});
|
||||
|
||||
saveDashboardLayout(layout);
|
||||
renderDashboardGrid();
|
||||
}}
|
||||
|
||||
function openWidgetSettings(widgetId) {{
|
||||
const modal = document.getElementById('widget-settings-modal');
|
||||
const title = document.getElementById('widget-settings-title');
|
||||
const body = document.getElementById('widget-settings-body');
|
||||
|
||||
if (!modal || !title || !body) return;
|
||||
|
||||
const widget = WidgetRegistry[widgetId];
|
||||
if (!widget) return;
|
||||
|
||||
title.textContent = widget.name + ' - Einstellungen';
|
||||
body.innerHTML = `
|
||||
<p style="color: var(--bp-text-muted); font-size: 13px; text-align: center; padding: 24px;">
|
||||
Widget-Einstellungen werden in einer zukuenftigen Version verfuegbar sein.
|
||||
</p>
|
||||
`;
|
||||
|
||||
modal.classList.add('active');
|
||||
}}
|
||||
|
||||
function closeWidgetSettings() {{
|
||||
const modal = document.getElementById('widget-settings-modal');
|
||||
if (modal) {{
|
||||
modal.classList.remove('active');
|
||||
}}
|
||||
}}
|
||||
|
||||
function loadLehrerDashboardModule() {{
|
||||
if (lehrerDashboardInitialized) {{
|
||||
console.log('Lehrer-Dashboard already initialized');
|
||||
return;
|
||||
}}
|
||||
|
||||
console.log('Loading Lehrer-Dashboard Module...');
|
||||
|
||||
renderDashboardHeader();
|
||||
renderWidgetCatalog();
|
||||
renderDashboardGrid();
|
||||
|
||||
lehrerDashboardInitialized = true;
|
||||
console.log('Lehrer-Dashboard Module loaded successfully');
|
||||
}}
|
||||
|
||||
// Widget JavaScript
|
||||
{widget_js}
|
||||
"""
|
||||
Reference in New Issue
Block a user