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>
890 lines
23 KiB
Python
890 lines
23 KiB
Python
"""
|
|
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}
|
|
"""
|