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>
2149 lines
64 KiB
Python
2149 lines
64 KiB
Python
"""
|
|
BreakPilot Studio - Messenger Modul
|
|
|
|
Funktionen:
|
|
- Matrix Chat Integration (DSGVO-konform)
|
|
- Direktnachrichten an Eltern
|
|
- Gruppenchats (Klassen, Fachschaften)
|
|
- Vorlagen fuer haeufige Nachrichten
|
|
- Dateiaustausch
|
|
- Lesebestaetigungen
|
|
"""
|
|
|
|
|
|
class MessengerModule:
|
|
"""Modul fuer sichere Kommunikation mit Matrix-Integration."""
|
|
|
|
@staticmethod
|
|
def get_css() -> str:
|
|
"""CSS fuer das Messenger-Modul."""
|
|
return """
|
|
/* =============================================
|
|
MESSENGER MODULE - Matrix Chat Integration
|
|
============================================= */
|
|
|
|
/* Panel Layout */
|
|
.panel-messenger {
|
|
display: none;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background: var(--bp-bg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.panel-messenger.active {
|
|
display: flex;
|
|
}
|
|
|
|
/* Messenger Container */
|
|
.messenger-container {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Left Sidebar - Conversations */
|
|
.messenger-sidebar {
|
|
width: 320px;
|
|
border-right: 1px solid var(--bp-border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.messenger-sidebar-header {
|
|
padding: 20px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.messenger-sidebar-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
/* Search Box */
|
|
.messenger-search {
|
|
position: relative;
|
|
}
|
|
|
|
.messenger-search input {
|
|
width: 100%;
|
|
padding: 12px 16px 12px 40px;
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 10px;
|
|
background: var(--bp-bg);
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.messenger-search input:focus {
|
|
outline: none;
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.messenger-search-icon {
|
|
position: absolute;
|
|
left: 14px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--bp-text-muted);
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* Tab Navigation */
|
|
.messenger-tabs {
|
|
display: flex;
|
|
padding: 12px 20px;
|
|
gap: 8px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.messenger-tab {
|
|
flex: 1;
|
|
padding: 10px 16px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
background: transparent;
|
|
color: var(--bp-text-muted);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.messenger-tab:hover {
|
|
background: var(--bp-surface-elevated);
|
|
}
|
|
|
|
.messenger-tab.active {
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
.messenger-tab-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 18px;
|
|
height: 18px;
|
|
padding: 0 6px;
|
|
margin-left: 6px;
|
|
border-radius: 9px;
|
|
background: #ef4444;
|
|
color: white;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Conversation List */
|
|
.messenger-conversations {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.messenger-conversation {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 16px 20px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
border-bottom: 1px solid var(--bp-border-subtle);
|
|
}
|
|
|
|
.messenger-conversation:hover {
|
|
background: var(--bp-surface-elevated);
|
|
}
|
|
|
|
.messenger-conversation.active {
|
|
background: var(--bp-primary-soft);
|
|
border-left: 3px solid var(--bp-primary);
|
|
}
|
|
|
|
.messenger-conversation.unread {
|
|
background: rgba(59, 130, 246, 0.05);
|
|
}
|
|
|
|
.messenger-avatar {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
background: var(--bp-surface-elevated);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.messenger-avatar.online::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 2px;
|
|
right: 2px;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: #10b981;
|
|
border: 2px solid var(--bp-surface);
|
|
}
|
|
|
|
.messenger-avatar.group {
|
|
border-radius: 12px;
|
|
background: var(--bp-primary-soft);
|
|
}
|
|
|
|
.messenger-conversation-content {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.messenger-conversation-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.messenger-conversation-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.messenger-conversation-time {
|
|
font-size: 11px;
|
|
color: var(--bp-text-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.messenger-conversation-preview {
|
|
font-size: 13px;
|
|
color: var(--bp-text-muted);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.messenger-conversation.unread .messenger-conversation-preview {
|
|
color: var(--bp-text);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.messenger-unread-badge {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* New Conversation Button */
|
|
.messenger-new-btn {
|
|
margin: 16px 20px;
|
|
padding: 14px;
|
|
border: none;
|
|
border-radius: 12px;
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.messenger-new-btn:hover {
|
|
background: var(--bp-primary-hover);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Chat Area */
|
|
.messenger-chat {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--bp-bg);
|
|
}
|
|
|
|
.messenger-chat-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.messenger-chat-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.messenger-chat-name {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.messenger-chat-status {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.messenger-chat-status.online {
|
|
color: #10b981;
|
|
}
|
|
|
|
.messenger-chat-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.messenger-action-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: none;
|
|
border-radius: 10px;
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text-muted);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.messenger-action-btn:hover {
|
|
background: var(--bp-primary-soft);
|
|
color: var(--bp-primary);
|
|
}
|
|
|
|
/* Messages Area */
|
|
.messenger-messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.messenger-message {
|
|
display: flex;
|
|
gap: 12px;
|
|
max-width: 70%;
|
|
}
|
|
|
|
.messenger-message.sent {
|
|
align-self: flex-end;
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
.messenger-message-avatar {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
background: var(--bp-surface-elevated);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.messenger-message-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.messenger-message-bubble {
|
|
padding: 12px 16px;
|
|
border-radius: 18px;
|
|
background: var(--bp-surface);
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
border: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.messenger-message.sent .messenger-message-bubble {
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
border-color: var(--bp-primary);
|
|
border-bottom-right-radius: 4px;
|
|
}
|
|
|
|
.messenger-message:not(.sent) .messenger-message-bubble {
|
|
border-bottom-left-radius: 4px;
|
|
}
|
|
|
|
.messenger-message-meta {
|
|
display: flex;
|
|
gap: 8px;
|
|
font-size: 11px;
|
|
color: var(--bp-text-muted);
|
|
padding: 0 8px;
|
|
}
|
|
|
|
.messenger-message.sent .messenger-message-meta {
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.messenger-message-status {
|
|
color: #10b981;
|
|
}
|
|
|
|
/* Message Input */
|
|
.messenger-input-area {
|
|
padding: 16px 24px;
|
|
border-top: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
}
|
|
|
|
.messenger-input-wrapper {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.messenger-input-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.messenger-input-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: none;
|
|
border-radius: 10px;
|
|
background: transparent;
|
|
color: var(--bp-text-muted);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.messenger-input-btn:hover {
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-primary);
|
|
}
|
|
|
|
.messenger-input {
|
|
flex: 1;
|
|
padding: 12px 16px;
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 20px;
|
|
background: var(--bp-bg);
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
resize: none;
|
|
min-height: 44px;
|
|
max-height: 120px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.messenger-input:focus {
|
|
outline: none;
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.messenger-send-btn {
|
|
width: 44px;
|
|
height: 44px;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.messenger-send-btn:hover {
|
|
background: var(--bp-primary-hover);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.messenger-send-btn:disabled {
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text-muted);
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
/* Empty State */
|
|
.messenger-empty {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 48px;
|
|
text-align: center;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.messenger-empty-icon {
|
|
font-size: 64px;
|
|
margin-bottom: 24px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.messenger-empty h2 {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.messenger-empty p {
|
|
font-size: 14px;
|
|
max-width: 400px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
/* Templates Panel */
|
|
.messenger-templates {
|
|
position: absolute;
|
|
bottom: 80px;
|
|
left: 24px;
|
|
right: 24px;
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
display: none;
|
|
}
|
|
|
|
.messenger-templates.active {
|
|
display: block;
|
|
}
|
|
|
|
.messenger-templates-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.messenger-template-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.messenger-template-item {
|
|
padding: 12px;
|
|
border-radius: 10px;
|
|
background: var(--bp-bg);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.messenger-template-item:hover {
|
|
background: var(--bp-primary-soft);
|
|
}
|
|
|
|
.messenger-template-name {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.messenger-template-preview {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* Connection Status */
|
|
.messenger-connection {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 12px 20px;
|
|
background: var(--bp-surface-elevated);
|
|
border-bottom: 1px solid var(--bp-border);
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.messenger-connection-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #10b981;
|
|
}
|
|
|
|
.messenger-connection-dot.connecting {
|
|
background: #f59e0b;
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
.messenger-connection-dot.offline {
|
|
background: #ef4444;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
/* Date Separator */
|
|
.messenger-date-separator {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.messenger-date-separator::before,
|
|
.messenger-date-separator::after {
|
|
content: '';
|
|
flex: 1;
|
|
height: 1px;
|
|
background: var(--bp-border);
|
|
}
|
|
|
|
.messenger-date-separator span {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
padding: 4px 12px;
|
|
background: var(--bp-surface-elevated);
|
|
border-radius: 12px;
|
|
}
|
|
|
|
/* Typing Indicator */
|
|
.messenger-typing {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 16px;
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.messenger-typing-dots {
|
|
display: flex;
|
|
gap: 4px;
|
|
}
|
|
|
|
.messenger-typing-dots span {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: var(--bp-text-muted);
|
|
animation: typingDot 1.4s infinite;
|
|
}
|
|
|
|
.messenger-typing-dots span:nth-child(2) {
|
|
animation-delay: 0.2s;
|
|
}
|
|
|
|
.messenger-typing-dots span:nth-child(3) {
|
|
animation-delay: 0.4s;
|
|
}
|
|
|
|
@keyframes typingDot {
|
|
0%, 60%, 100% { transform: translateY(0); }
|
|
30% { transform: translateY(-4px); }
|
|
}
|
|
|
|
/* Modal Styles */
|
|
.messenger-modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 10000;
|
|
}
|
|
|
|
.messenger-modal {
|
|
background: var(--bp-surface);
|
|
border-radius: 16px;
|
|
width: 90%;
|
|
max-width: 480px;
|
|
max-height: 80vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.messenger-modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 20px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.messenger-modal-header h3 {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.messenger-modal-close {
|
|
width: 32px;
|
|
height: 32px;
|
|
border: none;
|
|
background: var(--bp-surface-elevated);
|
|
border-radius: 8px;
|
|
font-size: 20px;
|
|
color: var(--bp-text-muted);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.messenger-modal-close:hover {
|
|
background: var(--bp-primary-soft);
|
|
color: var(--bp-primary);
|
|
}
|
|
|
|
.messenger-modal-search {
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.messenger-modal-search input {
|
|
width: 100%;
|
|
padding: 12px 16px;
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 10px;
|
|
background: var(--bp-bg);
|
|
color: var(--bp-text);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.messenger-modal-search input:focus {
|
|
outline: none;
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.messenger-modal-content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.messenger-contact-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 20px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.messenger-contact-item:hover {
|
|
background: var(--bp-surface-elevated);
|
|
}
|
|
|
|
.messenger-contact-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.messenger-contact-name {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.messenger-contact-role {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* Admin Tab Styles */
|
|
.messenger-admin-tab {
|
|
padding: 8px 16px;
|
|
background: var(--bp-surface-elevated);
|
|
color: var(--bp-text-muted);
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
margin: 8px 20px;
|
|
}
|
|
|
|
.messenger-admin-tab:hover {
|
|
background: var(--bp-primary-soft);
|
|
color: var(--bp-primary);
|
|
}
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_html() -> str:
|
|
"""HTML fuer das Messenger-Modul."""
|
|
return """
|
|
<!-- Messenger Panel -->
|
|
<div id="panel-messenger" class="panel-messenger" style="display: none;">
|
|
<div class="messenger-container">
|
|
<!-- Left Sidebar -->
|
|
<div class="messenger-sidebar">
|
|
<div class="messenger-sidebar-header">
|
|
<div class="messenger-sidebar-title">Nachrichten</div>
|
|
<div class="messenger-search">
|
|
<span class="messenger-search-icon">🔍</span>
|
|
<input type="text" placeholder="Suchen..." id="messenger-search-input">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Connection Status -->
|
|
<div class="messenger-connection">
|
|
<span class="messenger-connection-dot" id="messenger-connection-dot"></span>
|
|
<span id="messenger-connection-text">Verbunden mit Matrix</span>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="messenger-tabs">
|
|
<button class="messenger-tab active" data-tab="all" onclick="switchMessengerTab('all')">
|
|
Alle
|
|
<span class="messenger-tab-badge" id="messenger-all-badge" style="display: none;">0</span>
|
|
</button>
|
|
<button class="messenger-tab" data-tab="parents" onclick="switchMessengerTab('parents')">
|
|
Eltern
|
|
</button>
|
|
<button class="messenger-tab" data-tab="groups" onclick="switchMessengerTab('groups')">
|
|
Gruppen
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Conversation List -->
|
|
<div class="messenger-conversations" id="messenger-conversations">
|
|
<!-- Demo Conversations -->
|
|
<div class="messenger-conversation unread" data-id="1" onclick="openConversation(1)">
|
|
<div class="messenger-avatar online">👱</div>
|
|
<div class="messenger-conversation-content">
|
|
<div class="messenger-conversation-header">
|
|
<span class="messenger-conversation-name">Familie Mueller</span>
|
|
<span class="messenger-conversation-time">10:24</span>
|
|
</div>
|
|
<div class="messenger-conversation-preview">Vielen Dank fuer die Rueckmeldung...</div>
|
|
</div>
|
|
<span class="messenger-unread-badge">2</span>
|
|
</div>
|
|
|
|
<div class="messenger-conversation" data-id="2" onclick="openConversation(2)">
|
|
<div class="messenger-avatar online">👪</div>
|
|
<div class="messenger-conversation-content">
|
|
<div class="messenger-conversation-header">
|
|
<span class="messenger-conversation-name">Familie Schmidt</span>
|
|
<span class="messenger-conversation-time">Gestern</span>
|
|
</div>
|
|
<div class="messenger-conversation-preview">Ok, bis morgen dann!</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="messenger-conversation" data-id="3" onclick="openConversation(3)">
|
|
<div class="messenger-avatar group">👥</div>
|
|
<div class="messenger-conversation-content">
|
|
<div class="messenger-conversation-header">
|
|
<span class="messenger-conversation-name">Klasse 10a</span>
|
|
<span class="messenger-conversation-time">Mo</span>
|
|
</div>
|
|
<div class="messenger-conversation-preview">Reminder: Klassenfahrt naechste Woche</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="messenger-conversation" data-id="4" onclick="openConversation(4)">
|
|
<div class="messenger-avatar group">📚</div>
|
|
<div class="messenger-conversation-content">
|
|
<div class="messenger-conversation-header">
|
|
<span class="messenger-conversation-name">Fachschaft Deutsch</span>
|
|
<span class="messenger-conversation-time">Fr</span>
|
|
</div>
|
|
<div class="messenger-conversation-preview">Neue Lektuere-Empfehlungen</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New Conversation Button -->
|
|
<button class="messenger-new-btn" onclick="showNewConversationModal()">
|
|
<span>➕</span> Neue Nachricht
|
|
</button>
|
|
|
|
<!-- Admin Button -->
|
|
<button class="messenger-admin-tab" onclick="showMessengerAdmin()">
|
|
⚙ Kontakte verwalten
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Chat Area -->
|
|
<div class="messenger-chat" id="messenger-chat">
|
|
<!-- Empty State (shown when no conversation selected) -->
|
|
<div class="messenger-empty" id="messenger-empty-state">
|
|
<div class="messenger-empty-icon">💬</div>
|
|
<h2>Sichere Kommunikation</h2>
|
|
<p>Waehle eine Konversation aus oder starte eine neue Nachricht. Alle Nachrichten sind DSGVO-konform verschluesselt.</p>
|
|
<button class="messenger-new-btn" onclick="showNewConversationModal()">
|
|
<span>➕</span> Neue Nachricht
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Chat Content (hidden by default) -->
|
|
<div id="messenger-chat-content" style="display: none; flex-direction: column; flex: 1;">
|
|
<!-- Chat Header -->
|
|
<div class="messenger-chat-header">
|
|
<div class="messenger-chat-info">
|
|
<div class="messenger-avatar" id="messenger-chat-avatar">👱</div>
|
|
<div>
|
|
<div class="messenger-chat-name" id="messenger-chat-name">Familie Mueller</div>
|
|
<div class="messenger-chat-status online" id="messenger-chat-status">Online</div>
|
|
</div>
|
|
</div>
|
|
<div class="messenger-chat-actions">
|
|
<button class="messenger-action-btn" title="Videoanruf" onclick="startVideoCall()">🎥</button>
|
|
<button class="messenger-action-btn" title="Telefonanruf" onclick="startVoiceCall()">📞</button>
|
|
<button class="messenger-action-btn" title="Info" onclick="showConversationInfo()">ⓘ</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Messages -->
|
|
<div class="messenger-messages" id="messenger-messages">
|
|
<div class="messenger-date-separator">
|
|
<span>Heute</span>
|
|
</div>
|
|
|
|
<div class="messenger-message">
|
|
<div class="messenger-message-avatar">👱</div>
|
|
<div class="messenger-message-content">
|
|
<div class="messenger-message-bubble">
|
|
Guten Morgen! Ich wollte mich erkundigen, wie es mit den Hausaufgaben von Max laeuft?
|
|
</div>
|
|
<div class="messenger-message-meta">
|
|
<span>10:15</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="messenger-message sent">
|
|
<div class="messenger-message-content">
|
|
<div class="messenger-message-bubble">
|
|
Guten Morgen Frau Mueller! Max macht gute Fortschritte. Die letzten Aufgaben waren sehr gut erledigt.
|
|
</div>
|
|
<div class="messenger-message-meta">
|
|
<span>10:20</span>
|
|
<span class="messenger-message-status">✓✓</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="messenger-message">
|
|
<div class="messenger-message-avatar">👱</div>
|
|
<div class="messenger-message-content">
|
|
<div class="messenger-message-bubble">
|
|
Das freut mich sehr zu hoeren! Vielen Dank fuer die Rueckmeldung. Sollen wir vielleicht einen Termin fuer ein Elterngespraech vereinbaren?
|
|
</div>
|
|
<div class="messenger-message-meta">
|
|
<span>10:24</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Typing Indicator -->
|
|
<div class="messenger-typing" id="messenger-typing" style="display: none;">
|
|
<div class="messenger-typing-dots">
|
|
<span></span><span></span><span></span>
|
|
</div>
|
|
<span id="messenger-typing-name">Familie Mueller tippt...</span>
|
|
</div>
|
|
|
|
<!-- Input Area -->
|
|
<div class="messenger-input-area">
|
|
<div class="messenger-input-wrapper">
|
|
<div class="messenger-input-actions">
|
|
<button class="messenger-input-btn" title="Datei anhaengen" onclick="attachFile()">📎</button>
|
|
<button class="messenger-input-btn" title="Vorlagen" onclick="toggleTemplates()">📋</button>
|
|
</div>
|
|
<textarea
|
|
class="messenger-input"
|
|
id="messenger-input"
|
|
placeholder="Nachricht schreiben..."
|
|
rows="1"
|
|
onkeydown="handleMessageKeydown(event)"
|
|
oninput="adjustTextareaHeight(this)"
|
|
></textarea>
|
|
<button class="messenger-send-btn" id="messenger-send-btn" onclick="sendMessage()" disabled>
|
|
➤
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Templates Panel -->
|
|
<div class="messenger-templates" id="messenger-templates">
|
|
<div class="messenger-templates-title">Schnellvorlagen</div>
|
|
<div class="messenger-template-list">
|
|
<div class="messenger-template-item" onclick="useTemplate(0)">
|
|
<div class="messenger-template-name">Terminbestaetigung</div>
|
|
<div class="messenger-template-preview">Vielen Dank fuer Ihre Terminanfrage. Ich bestaet...</div>
|
|
</div>
|
|
<div class="messenger-template-item" onclick="useTemplate(1)">
|
|
<div class="messenger-template-name">Hausaufgaben-Info</div>
|
|
<div class="messenger-template-preview">Zur Information: Die Hausaufgaben fuer diese Woche...</div>
|
|
</div>
|
|
<div class="messenger-template-item" onclick="useTemplate(2)">
|
|
<div class="messenger-template-name">Entschuldigung bestaetigen</div>
|
|
<div class="messenger-template-preview">Ich bestaettige den Erhalt der Entschuldigung...</div>
|
|
</div>
|
|
<div class="messenger-template-item" onclick="useTemplate(3)">
|
|
<div class="messenger-template-name">Gespraechsanfrage</div>
|
|
<div class="messenger-template-preview">Ich wuerde gerne einen Termin fuer ein Gespraech...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_js() -> str:
|
|
"""JavaScript fuer das Messenger-Modul."""
|
|
return """
|
|
// =============================================
|
|
// MESSENGER MODULE - Matrix Chat Integration
|
|
// =============================================
|
|
|
|
let messengerInitialized = false;
|
|
let currentConversationId = null;
|
|
let messengerSocket = null;
|
|
let messengerContacts = [];
|
|
let messengerConversations = [];
|
|
let messengerMessages = {};
|
|
let messengerTemplates = [];
|
|
let messengerGroups = [];
|
|
|
|
// API Base URL
|
|
const MESSENGER_API = '/api/messenger';
|
|
|
|
// Fallback Templates (wenn API nicht erreichbar)
|
|
const defaultTemplates = [
|
|
{
|
|
id: 'default-1',
|
|
name: 'Terminbestaetigung',
|
|
content: 'Vielen Dank fuer Ihre Terminanfrage. Ich bestaetige den Termin am [DATUM] um [UHRZEIT]. Bitte geben Sie mir Bescheid, falls sich etwas aendern sollte.',
|
|
category: 'termin'
|
|
},
|
|
{
|
|
id: 'default-2',
|
|
name: 'Hausaufgaben-Info',
|
|
content: 'Zur Information: Die Hausaufgaben fuer diese Woche umfassen [THEMA]. Abgabetermin ist [DATUM]. Bei Fragen stehe ich gerne zur Verfuegung.',
|
|
category: 'hausaufgaben'
|
|
},
|
|
{
|
|
id: 'default-3',
|
|
name: 'Entschuldigung bestaetigen',
|
|
content: 'Ich bestaetige den Erhalt der Entschuldigung fuer [NAME] am [DATUM]. Die Fehlzeiten wurden entsprechend vermerkt.',
|
|
category: 'entschuldigung'
|
|
},
|
|
{
|
|
id: 'default-4',
|
|
name: 'Gespraechsanfrage',
|
|
content: 'Ich wuerde gerne einen Termin fuer ein Gespraech mit Ihnen vereinbaren, um [THEMA] zu besprechen. Waeren Sie am [DATUM] um [UHRZEIT] verfuegbar?',
|
|
category: 'gespraech'
|
|
}
|
|
];
|
|
|
|
async function loadMessengerModule() {
|
|
if (messengerInitialized) {
|
|
console.log('Messenger module already initialized');
|
|
return;
|
|
}
|
|
|
|
console.log('Loading Messenger Module...');
|
|
|
|
// Initialize search
|
|
const searchInput = document.getElementById('messenger-search-input');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', (e) => {
|
|
filterConversations(e.target.value);
|
|
});
|
|
}
|
|
|
|
// Initialize message input
|
|
const messageInput = document.getElementById('messenger-input');
|
|
if (messageInput) {
|
|
messageInput.addEventListener('input', () => {
|
|
const sendBtn = document.getElementById('messenger-send-btn');
|
|
if (sendBtn) {
|
|
sendBtn.disabled = !messageInput.value.trim();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load data from API
|
|
await loadMessengerData();
|
|
|
|
// Connect to Matrix (mock)
|
|
connectToMatrix();
|
|
|
|
messengerInitialized = true;
|
|
console.log('Messenger Module loaded successfully');
|
|
}
|
|
|
|
// Load all messenger data from API
|
|
async function loadMessengerData() {
|
|
try {
|
|
// Load contacts, conversations, templates, groups in parallel
|
|
const [contactsRes, convsRes, templatesRes, groupsRes] = await Promise.all([
|
|
fetch(`${MESSENGER_API}/contacts`),
|
|
fetch(`${MESSENGER_API}/conversations`),
|
|
fetch(`${MESSENGER_API}/templates`),
|
|
fetch(`${MESSENGER_API}/groups`)
|
|
]);
|
|
|
|
if (contactsRes.ok) {
|
|
messengerContacts = await contactsRes.json();
|
|
console.log(`Loaded ${messengerContacts.length} contacts`);
|
|
}
|
|
|
|
if (convsRes.ok) {
|
|
messengerConversations = await convsRes.json();
|
|
console.log(`Loaded ${messengerConversations.length} conversations`);
|
|
renderConversationList();
|
|
}
|
|
|
|
if (templatesRes.ok) {
|
|
messengerTemplates = await templatesRes.json();
|
|
console.log(`Loaded ${messengerTemplates.length} templates`);
|
|
} else {
|
|
messengerTemplates = defaultTemplates;
|
|
}
|
|
renderTemplateList();
|
|
|
|
if (groupsRes.ok) {
|
|
messengerGroups = await groupsRes.json();
|
|
console.log(`Loaded ${messengerGroups.length} groups`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error loading messenger data:', error);
|
|
messengerTemplates = defaultTemplates;
|
|
renderTemplateList();
|
|
}
|
|
}
|
|
|
|
// Render conversation list from API data
|
|
function renderConversationList() {
|
|
const container = document.getElementById('messenger-conversations');
|
|
if (!container || messengerConversations.length === 0) return;
|
|
|
|
// Keep demo conversations if no API data
|
|
if (messengerConversations.length === 0) return;
|
|
|
|
container.innerHTML = messengerConversations.map(conv => {
|
|
const isGroup = conv.type === 'group';
|
|
const unreadCount = conv.unread_count || 0;
|
|
const isUnread = unreadCount > 0;
|
|
const avatar = isGroup ? '👥' : '👱';
|
|
const lastTime = conv.last_message_time ? formatMessageTime(conv.last_message_time) : '';
|
|
|
|
return `
|
|
<div class="messenger-conversation${isUnread ? ' unread' : ''}"
|
|
data-id="${conv.id}"
|
|
data-type="${conv.type}"
|
|
onclick="openConversation('${conv.id}')">
|
|
<div class="messenger-avatar${isGroup ? ' group' : ''}${conv.online ? ' online' : ''}">${avatar}</div>
|
|
<div class="messenger-conversation-content">
|
|
<div class="messenger-conversation-header">
|
|
<span class="messenger-conversation-name">${escapeHtml(conv.name)}</span>
|
|
<span class="messenger-conversation-time">${lastTime}</span>
|
|
</div>
|
|
<div class="messenger-conversation-preview">${escapeHtml(conv.last_message || 'Keine Nachrichten')}</div>
|
|
</div>
|
|
${isUnread ? `<span class="messenger-unread-badge">${unreadCount}</span>` : ''}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Update badge count
|
|
const totalUnread = messengerConversations.reduce((sum, c) => sum + (c.unread_count || 0), 0);
|
|
const badge = document.getElementById('messenger-all-badge');
|
|
if (badge) {
|
|
badge.textContent = totalUnread;
|
|
badge.style.display = totalUnread > 0 ? 'inline-flex' : 'none';
|
|
}
|
|
}
|
|
|
|
// Render template list
|
|
function renderTemplateList() {
|
|
const container = document.querySelector('.messenger-template-list');
|
|
if (!container) return;
|
|
|
|
const templates = messengerTemplates.length > 0 ? messengerTemplates : defaultTemplates;
|
|
|
|
container.innerHTML = templates.map((tpl, index) => `
|
|
<div class="messenger-template-item" onclick="useTemplate('${tpl.id || index}')">
|
|
<div class="messenger-template-name">${escapeHtml(tpl.name)}</div>
|
|
<div class="messenger-template-preview">${escapeHtml(tpl.content.substring(0, 50))}...</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// Format message time
|
|
function formatMessageTime(isoTime) {
|
|
const date = new Date(isoTime);
|
|
const now = new Date();
|
|
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 0) {
|
|
return date.getHours().toString().padStart(2, '0') + ':' +
|
|
date.getMinutes().toString().padStart(2, '0');
|
|
} else if (diffDays === 1) {
|
|
return 'Gestern';
|
|
} else if (diffDays < 7) {
|
|
const days = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
|
|
return days[date.getDay()];
|
|
} else {
|
|
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
|
|
}
|
|
}
|
|
|
|
// Connect to Matrix server (mock)
|
|
function connectToMatrix() {
|
|
const dot = document.getElementById('messenger-connection-dot');
|
|
const text = document.getElementById('messenger-connection-text');
|
|
|
|
if (dot && text) {
|
|
dot.classList.add('connecting');
|
|
text.textContent = 'Verbinde mit Matrix...';
|
|
|
|
// Simulate connection
|
|
setTimeout(() => {
|
|
dot.classList.remove('connecting');
|
|
text.textContent = 'Verbunden mit Matrix';
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
// Switch between tabs
|
|
function switchMessengerTab(tab) {
|
|
// Update tab buttons
|
|
document.querySelectorAll('.messenger-tab').forEach(btn => {
|
|
btn.classList.toggle('active', btn.dataset.tab === tab);
|
|
});
|
|
|
|
// Filter conversations
|
|
const convs = document.querySelectorAll('.messenger-conversation');
|
|
convs.forEach(conv => {
|
|
if (tab === 'all') {
|
|
conv.style.display = 'flex';
|
|
} else if (tab === 'parents') {
|
|
const isGroup = conv.querySelector('.messenger-avatar.group');
|
|
conv.style.display = isGroup ? 'none' : 'flex';
|
|
} else if (tab === 'groups') {
|
|
const isGroup = conv.querySelector('.messenger-avatar.group');
|
|
conv.style.display = isGroup ? 'flex' : 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Filter conversations by search
|
|
function filterConversations(query) {
|
|
const convs = document.querySelectorAll('.messenger-conversation');
|
|
const lowerQuery = query.toLowerCase();
|
|
|
|
convs.forEach(conv => {
|
|
const name = conv.querySelector('.messenger-conversation-name')?.textContent.toLowerCase() || '';
|
|
const preview = conv.querySelector('.messenger-conversation-preview')?.textContent.toLowerCase() || '';
|
|
const matches = name.includes(lowerQuery) || preview.includes(lowerQuery);
|
|
conv.style.display = matches ? 'flex' : 'none';
|
|
});
|
|
}
|
|
|
|
// Open a conversation
|
|
async function openConversation(id) {
|
|
currentConversationId = id;
|
|
|
|
// Update active state
|
|
document.querySelectorAll('.messenger-conversation').forEach(conv => {
|
|
conv.classList.toggle('active', conv.dataset.id == id);
|
|
if (conv.dataset.id == id) {
|
|
conv.classList.remove('unread');
|
|
const badge = conv.querySelector('.messenger-unread-badge');
|
|
if (badge) badge.remove();
|
|
}
|
|
});
|
|
|
|
// Show chat content
|
|
const emptyState = document.getElementById('messenger-empty-state');
|
|
const chatContent = document.getElementById('messenger-chat-content');
|
|
|
|
if (emptyState) emptyState.style.display = 'none';
|
|
if (chatContent) chatContent.style.display = 'flex';
|
|
|
|
// Update chat header based on conversation
|
|
const conv = document.querySelector(`.messenger-conversation[data-id="${id}"]`);
|
|
if (conv) {
|
|
const name = conv.querySelector('.messenger-conversation-name')?.textContent;
|
|
const avatar = conv.querySelector('.messenger-avatar')?.innerHTML;
|
|
const isOnline = conv.querySelector('.messenger-avatar.online');
|
|
|
|
document.getElementById('messenger-chat-name').textContent = name;
|
|
document.getElementById('messenger-chat-avatar').innerHTML = avatar;
|
|
|
|
const status = document.getElementById('messenger-chat-status');
|
|
if (status) {
|
|
status.textContent = isOnline ? 'Online' : 'Offline';
|
|
status.className = 'messenger-chat-status' + (isOnline ? ' online' : '');
|
|
}
|
|
}
|
|
|
|
// Load messages from API
|
|
await loadConversationMessages(id);
|
|
|
|
// Focus input
|
|
document.getElementById('messenger-input')?.focus();
|
|
}
|
|
|
|
// Load messages for a conversation
|
|
async function loadConversationMessages(conversationId) {
|
|
const container = document.getElementById('messenger-messages');
|
|
if (!container) return;
|
|
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/conversations/${conversationId}/messages`);
|
|
if (!response.ok) {
|
|
console.error('Failed to load messages');
|
|
return;
|
|
}
|
|
|
|
const messages = await response.json();
|
|
messengerMessages[conversationId] = messages;
|
|
|
|
// Render messages
|
|
if (messages.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="messenger-date-separator">
|
|
<span>Keine Nachrichten</span>
|
|
</div>
|
|
`;
|
|
} else {
|
|
renderMessages(messages, container);
|
|
}
|
|
|
|
// Scroll to bottom
|
|
container.scrollTop = container.scrollHeight;
|
|
|
|
} catch (error) {
|
|
console.error('Error loading messages:', error);
|
|
}
|
|
}
|
|
|
|
// Render messages in container
|
|
function renderMessages(messages, container) {
|
|
let html = '';
|
|
let lastDate = null;
|
|
|
|
messages.forEach(msg => {
|
|
const msgDate = new Date(msg.created_at).toDateString();
|
|
|
|
// Add date separator if needed
|
|
if (msgDate !== lastDate) {
|
|
const dateLabel = formatDateLabel(msg.created_at);
|
|
html += `
|
|
<div class="messenger-date-separator">
|
|
<span>${dateLabel}</span>
|
|
</div>
|
|
`;
|
|
lastDate = msgDate;
|
|
}
|
|
|
|
const isSent = msg.sender_id === 'teacher'; // TODO: Get actual user ID
|
|
const time = formatMessageTime(msg.created_at);
|
|
|
|
html += `
|
|
<div class="messenger-message${isSent ? ' sent' : ''}">
|
|
${!isSent ? '<div class="messenger-message-avatar">👱</div>' : ''}
|
|
<div class="messenger-message-content">
|
|
<div class="messenger-message-bubble">${escapeHtml(msg.content)}</div>
|
|
<div class="messenger-message-meta">
|
|
<span>${time}</span>
|
|
${isSent && msg.read ? '<span class="messenger-message-status">✓✓</span>' : ''}
|
|
${isSent && !msg.read ? '<span class="messenger-message-status">✓</span>' : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
// Format date label for message separator
|
|
function formatDateLabel(isoTime) {
|
|
const date = new Date(isoTime);
|
|
const now = new Date();
|
|
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 0) return 'Heute';
|
|
if (diffDays === 1) return 'Gestern';
|
|
return date.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' });
|
|
}
|
|
|
|
// Send a message
|
|
async function sendMessage() {
|
|
const input = document.getElementById('messenger-input');
|
|
const message = input?.value.trim();
|
|
|
|
if (!message || !currentConversationId) return;
|
|
|
|
// Disable send button while sending
|
|
const sendBtn = document.getElementById('messenger-send-btn');
|
|
sendBtn.disabled = true;
|
|
|
|
try {
|
|
// Send message via API
|
|
const response = await fetch(`${MESSENGER_API}/conversations/${currentConversationId}/messages`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
content: message,
|
|
sender_id: 'teacher' // TODO: Get actual user ID
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to send message');
|
|
}
|
|
|
|
// Add message to UI
|
|
addMessageToUI(message, true);
|
|
|
|
// Clear input
|
|
input.value = '';
|
|
input.style.height = 'auto';
|
|
|
|
// Update conversation preview
|
|
updateConversationPreview(currentConversationId, message);
|
|
|
|
} catch (error) {
|
|
console.error('Error sending message:', error);
|
|
alert('Nachricht konnte nicht gesendet werden.');
|
|
}
|
|
|
|
sendBtn.disabled = false;
|
|
}
|
|
|
|
// Update conversation preview in sidebar
|
|
function updateConversationPreview(convId, message) {
|
|
const conv = document.querySelector(`.messenger-conversation[data-id="${convId}"]`);
|
|
if (conv) {
|
|
const preview = conv.querySelector('.messenger-conversation-preview');
|
|
if (preview) {
|
|
preview.textContent = message.substring(0, 50) + (message.length > 50 ? '...' : '');
|
|
}
|
|
const timeEl = conv.querySelector('.messenger-conversation-time');
|
|
if (timeEl) {
|
|
const now = new Date();
|
|
timeEl.textContent = now.getHours().toString().padStart(2, '0') + ':' +
|
|
now.getMinutes().toString().padStart(2, '0');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add message to UI
|
|
function addMessageToUI(text, isSent) {
|
|
const container = document.getElementById('messenger-messages');
|
|
if (!container) return;
|
|
|
|
const now = new Date();
|
|
const time = now.getHours().toString().padStart(2, '0') + ':' +
|
|
now.getMinutes().toString().padStart(2, '0');
|
|
|
|
const messageHtml = `
|
|
<div class="messenger-message${isSent ? ' sent' : ''}">
|
|
${!isSent ? '<div class="messenger-message-avatar">👱</div>' : ''}
|
|
<div class="messenger-message-content">
|
|
<div class="messenger-message-bubble">${escapeHtml(text)}</div>
|
|
<div class="messenger-message-meta">
|
|
<span>${time}</span>
|
|
${isSent ? '<span class="messenger-message-status">✓</span>' : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.insertAdjacentHTML('beforeend', messageHtml);
|
|
container.scrollTop = container.scrollHeight;
|
|
|
|
// Update conversation preview
|
|
if (currentConversationId) {
|
|
const conv = document.querySelector(`.messenger-conversation[data-id="${currentConversationId}"]`);
|
|
if (conv) {
|
|
const preview = conv.querySelector('.messenger-conversation-preview');
|
|
if (preview) {
|
|
preview.textContent = text.substring(0, 50) + (text.length > 50 ? '...' : '');
|
|
}
|
|
const timeEl = conv.querySelector('.messenger-conversation-time');
|
|
if (timeEl) {
|
|
timeEl.textContent = time;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show typing indicator
|
|
function showTypingIndicator(show) {
|
|
const indicator = document.getElementById('messenger-typing');
|
|
if (indicator) {
|
|
indicator.style.display = show ? 'flex' : 'none';
|
|
}
|
|
}
|
|
|
|
// Handle keyboard in message input
|
|
function handleMessageKeydown(event) {
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault();
|
|
sendMessage();
|
|
}
|
|
}
|
|
|
|
// Adjust textarea height
|
|
function adjustTextareaHeight(textarea) {
|
|
textarea.style.height = 'auto';
|
|
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
|
|
}
|
|
|
|
// Toggle templates panel
|
|
function toggleTemplates() {
|
|
const panel = document.getElementById('messenger-templates');
|
|
if (panel) {
|
|
panel.classList.toggle('active');
|
|
}
|
|
}
|
|
|
|
// Use a template
|
|
function useTemplate(templateId) {
|
|
const templates = messengerTemplates.length > 0 ? messengerTemplates : defaultTemplates;
|
|
const template = templates.find(t => t.id === templateId) ||
|
|
templates[parseInt(templateId)] ||
|
|
templates[0];
|
|
|
|
if (!template) return;
|
|
|
|
const input = document.getElementById('messenger-input');
|
|
if (input) {
|
|
input.value = template.content || template.text;
|
|
input.focus();
|
|
adjustTextareaHeight(input);
|
|
document.getElementById('messenger-send-btn').disabled = false;
|
|
}
|
|
|
|
toggleTemplates();
|
|
}
|
|
|
|
// Attach file
|
|
function attachFile() {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.accept = '.pdf,.doc,.docx,.jpg,.jpeg,.png';
|
|
input.onchange = (e) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) {
|
|
console.log('Attaching file:', file.name);
|
|
addMessageToUI(`[Datei: ${file.name}]`, true);
|
|
}
|
|
};
|
|
input.click();
|
|
}
|
|
|
|
// Start video call
|
|
function startVideoCall() {
|
|
if (!currentConversationId) return;
|
|
console.log('Starting video call...');
|
|
alert('Videokonferenz wird gestartet... (Demo)');
|
|
}
|
|
|
|
// Start voice call
|
|
function startVoiceCall() {
|
|
if (!currentConversationId) return;
|
|
console.log('Starting voice call...');
|
|
alert('Anruf wird gestartet... (Demo)');
|
|
}
|
|
|
|
// Show conversation info
|
|
function showConversationInfo() {
|
|
if (!currentConversationId) return;
|
|
console.log('Showing conversation info...');
|
|
alert('Konversations-Details (Demo)');
|
|
}
|
|
|
|
// Show new conversation modal
|
|
async function showNewConversationModal() {
|
|
console.log('Opening new conversation modal...');
|
|
|
|
// Show contact picker if contacts are loaded
|
|
if (messengerContacts.length > 0) {
|
|
showContactPicker();
|
|
} else {
|
|
// Fallback to prompt
|
|
const recipient = prompt('Empfaenger eingeben (Name oder E-Mail):');
|
|
if (recipient) {
|
|
await createNewConversation(recipient, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show contact picker modal
|
|
function showContactPicker() {
|
|
// Create modal HTML
|
|
const modalHtml = `
|
|
<div id="contact-picker-modal" class="messenger-modal-overlay" onclick="closeContactPicker(event)">
|
|
<div class="messenger-modal" onclick="event.stopPropagation()">
|
|
<div class="messenger-modal-header">
|
|
<h3>Kontakt auswaehlen</h3>
|
|
<button onclick="closeContactPicker()" class="messenger-modal-close">×</button>
|
|
</div>
|
|
<div class="messenger-modal-search">
|
|
<input type="text" placeholder="Kontakt suchen..." oninput="filterContactList(this.value)">
|
|
</div>
|
|
<div class="messenger-modal-content" id="contact-picker-list">
|
|
${messengerContacts.map(c => `
|
|
<div class="messenger-contact-item" onclick="selectContact('${c.id}')">
|
|
<div class="messenger-avatar">👱</div>
|
|
<div class="messenger-contact-info">
|
|
<div class="messenger-contact-name">${escapeHtml(c.name)}</div>
|
|
<div class="messenger-contact-role">${escapeHtml(c.student_name || '')} ${c.class_name ? '(' + c.class_name + ')' : ''}</div>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
}
|
|
|
|
// Close contact picker
|
|
function closeContactPicker(event) {
|
|
if (event && event.target !== event.currentTarget) return;
|
|
const modal = document.getElementById('contact-picker-modal');
|
|
if (modal) modal.remove();
|
|
}
|
|
|
|
// Filter contact list in picker
|
|
function filterContactList(query) {
|
|
const lowerQuery = query.toLowerCase();
|
|
document.querySelectorAll('.messenger-contact-item').forEach(item => {
|
|
const name = item.querySelector('.messenger-contact-name')?.textContent.toLowerCase() || '';
|
|
const role = item.querySelector('.messenger-contact-role')?.textContent.toLowerCase() || '';
|
|
item.style.display = (name.includes(lowerQuery) || role.includes(lowerQuery)) ? 'flex' : 'none';
|
|
});
|
|
}
|
|
|
|
// Select a contact and create conversation
|
|
async function selectContact(contactId) {
|
|
closeContactPicker();
|
|
const contact = messengerContacts.find(c => c.id === contactId);
|
|
if (contact) {
|
|
await createNewConversation(contact.name, [contact.id]);
|
|
}
|
|
}
|
|
|
|
// Create new conversation via API
|
|
async function createNewConversation(name, participantIds) {
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/conversations`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
name: name,
|
|
type: 'direct',
|
|
participant_ids: participantIds || []
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to create conversation');
|
|
}
|
|
|
|
const newConv = await response.json();
|
|
messengerConversations.unshift(newConv);
|
|
renderConversationList();
|
|
openConversation(newConv.id);
|
|
|
|
} catch (error) {
|
|
console.error('Error creating conversation:', error);
|
|
|
|
// Fallback: Create local conversation
|
|
const newId = 'local-' + Date.now();
|
|
const newConv = {
|
|
id: newId,
|
|
name: name,
|
|
type: 'direct',
|
|
last_message: 'Neue Konversation',
|
|
last_message_time: new Date().toISOString()
|
|
};
|
|
messengerConversations.unshift(newConv);
|
|
renderConversationList();
|
|
openConversation(newId);
|
|
}
|
|
}
|
|
|
|
// Escape HTML
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Show Messenger Panel
|
|
function showMessengerPanel() {
|
|
console.log('showMessengerPanel called');
|
|
hideAllPanels();
|
|
const panel = document.getElementById('panel-messenger');
|
|
if (panel) {
|
|
panel.style.display = 'flex';
|
|
panel.classList.add('active');
|
|
loadMessengerModule();
|
|
console.log('Messenger panel shown');
|
|
} else {
|
|
console.error('panel-messenger not found');
|
|
}
|
|
}
|
|
|
|
// =============================================
|
|
// MESSENGER ADMIN FUNCTIONS
|
|
// =============================================
|
|
|
|
// Show Messenger Admin Modal
|
|
function showMessengerAdmin() {
|
|
const modalHtml = `
|
|
<div id="messenger-admin-modal" class="messenger-modal-overlay" onclick="closeMessengerAdmin(event)">
|
|
<div class="messenger-modal" style="max-width: 700px;" onclick="event.stopPropagation()">
|
|
<div class="messenger-modal-header">
|
|
<h3>Kontakte verwalten</h3>
|
|
<button onclick="closeMessengerAdmin()" class="messenger-modal-close">×</button>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 8px; padding: 16px 20px; border-bottom: 1px solid var(--bp-border);">
|
|
<button class="messenger-new-btn" style="margin: 0; flex: 1;" onclick="showAddContactForm()">
|
|
<span>➕</span> Neuer Kontakt
|
|
</button>
|
|
<button class="messenger-new-btn" style="margin: 0; flex: 1; background: var(--bp-success, #10b981);" onclick="showCSVImport()">
|
|
<span>📂</span> CSV Import
|
|
</button>
|
|
<button class="messenger-new-btn" style="margin: 0; flex: 1; background: var(--bp-surface-elevated); color: var(--bp-text);" onclick="exportContacts()">
|
|
<span>💾</span> Export
|
|
</button>
|
|
</div>
|
|
|
|
<div class="messenger-modal-search">
|
|
<input type="text" placeholder="Kontakte suchen..." oninput="filterAdminContacts(this.value)">
|
|
</div>
|
|
|
|
<div class="messenger-modal-content" id="admin-contact-list" style="max-height: 400px;">
|
|
<div style="text-align: center; padding: 40px; color: var(--bp-text-muted);">
|
|
<p>Lade Kontakte...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="padding: 16px 20px; border-top: 1px solid var(--bp-border); font-size: 12px; color: var(--bp-text-muted);">
|
|
${messengerContacts.length} Kontakte
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
loadAdminContacts();
|
|
}
|
|
|
|
// Close admin modal
|
|
function closeMessengerAdmin(event) {
|
|
if (event && event.target !== event.currentTarget) return;
|
|
const modal = document.getElementById('messenger-admin-modal');
|
|
if (modal) modal.remove();
|
|
}
|
|
|
|
// Load contacts for admin panel
|
|
async function loadAdminContacts() {
|
|
const container = document.getElementById('admin-contact-list');
|
|
if (!container) return;
|
|
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/contacts`);
|
|
if (response.ok) {
|
|
messengerContacts = await response.json();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading contacts:', error);
|
|
}
|
|
|
|
if (messengerContacts.length === 0) {
|
|
container.innerHTML = `
|
|
<div style="text-align: center; padding: 40px; color: var(--bp-text-muted);">
|
|
<p>Keine Kontakte vorhanden.</p>
|
|
<p>Fuegen Sie Kontakte hinzu oder importieren Sie eine CSV-Datei.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = messengerContacts.map(c => `
|
|
<div class="messenger-contact-item" data-id="${c.id}">
|
|
<div class="messenger-avatar">👱</div>
|
|
<div class="messenger-contact-info" style="flex: 1;">
|
|
<div class="messenger-contact-name">${escapeHtml(c.name)}</div>
|
|
<div class="messenger-contact-role">
|
|
${escapeHtml(c.email || '')}
|
|
${c.student_name ? ' | Schueler: ' + escapeHtml(c.student_name) : ''}
|
|
${c.class_name ? ' (' + c.class_name + ')' : ''}
|
|
</div>
|
|
</div>
|
|
<button onclick="editContact('${c.id}')" style="background: none; border: none; cursor: pointer; font-size: 16px; padding: 8px;" title="Bearbeiten">✎</button>
|
|
<button onclick="deleteContact('${c.id}')" style="background: none; border: none; cursor: pointer; font-size: 16px; padding: 8px; color: #ef4444;" title="Loeschen">🗑</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
// Filter contacts in admin panel
|
|
function filterAdminContacts(query) {
|
|
const lowerQuery = query.toLowerCase();
|
|
document.querySelectorAll('#admin-contact-list .messenger-contact-item').forEach(item => {
|
|
const name = item.querySelector('.messenger-contact-name')?.textContent.toLowerCase() || '';
|
|
const role = item.querySelector('.messenger-contact-role')?.textContent.toLowerCase() || '';
|
|
item.style.display = (name.includes(lowerQuery) || role.includes(lowerQuery)) ? 'flex' : 'none';
|
|
});
|
|
}
|
|
|
|
// Show add contact form
|
|
function showAddContactForm() {
|
|
const formHtml = `
|
|
<div id="add-contact-form" class="messenger-modal-overlay" onclick="closeAddContactForm(event)">
|
|
<div class="messenger-modal" style="max-width: 500px;" onclick="event.stopPropagation()">
|
|
<div class="messenger-modal-header">
|
|
<h3>Neuer Kontakt</h3>
|
|
<button onclick="closeAddContactForm()" class="messenger-modal-close">×</button>
|
|
</div>
|
|
<form onsubmit="saveNewContact(event)" style="padding: 20px;">
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Name *</label>
|
|
<input type="text" name="name" required style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">E-Mail</label>
|
|
<input type="email" name="email" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Telefon</label>
|
|
<input type="tel" name="phone" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Schueler-Name</label>
|
|
<input type="text" name="student_name" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Klasse</label>
|
|
<input type="text" name="class_name" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Typ</label>
|
|
<select name="contact_type" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
<option value="parent">Eltern</option>
|
|
<option value="teacher">Lehrer</option>
|
|
<option value="student">Schueler</option>
|
|
<option value="other">Sonstige</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="messenger-new-btn" style="width: 100%; margin: 0;">Kontakt speichern</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.insertAdjacentHTML('beforeend', formHtml);
|
|
}
|
|
|
|
function closeAddContactForm(event) {
|
|
if (event && event.target !== event.currentTarget) return;
|
|
const form = document.getElementById('add-contact-form');
|
|
if (form) form.remove();
|
|
}
|
|
|
|
async function saveNewContact(event) {
|
|
event.preventDefault();
|
|
const form = event.target;
|
|
const data = {
|
|
name: form.name.value,
|
|
email: form.email.value || null,
|
|
phone: form.phone.value || null,
|
|
student_name: form.student_name.value || null,
|
|
class_name: form.class_name.value || null,
|
|
contact_type: form.contact_type.value
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/contacts`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeAddContactForm();
|
|
loadAdminContacts();
|
|
loadMessengerData(); // Refresh main data
|
|
} else {
|
|
const error = await response.json();
|
|
alert('Fehler: ' + (error.detail || 'Unbekannter Fehler'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving contact:', error);
|
|
alert('Kontakt konnte nicht gespeichert werden.');
|
|
}
|
|
}
|
|
|
|
// Delete contact
|
|
async function deleteContact(contactId) {
|
|
if (!confirm('Kontakt wirklich loeschen?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/contacts/${contactId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (response.ok) {
|
|
loadAdminContacts();
|
|
loadMessengerData();
|
|
} else {
|
|
alert('Kontakt konnte nicht geloescht werden.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting contact:', error);
|
|
alert('Fehler beim Loeschen.');
|
|
}
|
|
}
|
|
|
|
// Edit contact
|
|
async function editContact(contactId) {
|
|
const contact = messengerContacts.find(c => c.id === contactId);
|
|
if (!contact) return;
|
|
|
|
const formHtml = `
|
|
<div id="edit-contact-form" class="messenger-modal-overlay" onclick="closeEditContactForm(event)">
|
|
<div class="messenger-modal" style="max-width: 500px;" onclick="event.stopPropagation()">
|
|
<div class="messenger-modal-header">
|
|
<h3>Kontakt bearbeiten</h3>
|
|
<button onclick="closeEditContactForm()" class="messenger-modal-close">×</button>
|
|
</div>
|
|
<form onsubmit="updateContact(event, '${contactId}')" style="padding: 20px;">
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Name *</label>
|
|
<input type="text" name="name" value="${escapeHtml(contact.name)}" required style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">E-Mail</label>
|
|
<input type="email" name="email" value="${escapeHtml(contact.email || '')}" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Telefon</label>
|
|
<input type="tel" name="phone" value="${escapeHtml(contact.phone || '')}" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Schueler-Name</label>
|
|
<input type="text" name="student_name" value="${escapeHtml(contact.student_name || '')}" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<div style="margin-bottom: 16px;">
|
|
<label style="display: block; margin-bottom: 4px; font-size: 13px; color: var(--bp-text-muted);">Klasse</label>
|
|
<input type="text" name="class_name" value="${escapeHtml(contact.class_name || '')}" style="width: 100%; padding: 10px; border: 1px solid var(--bp-border); border-radius: 8px; background: var(--bp-bg); color: var(--bp-text);">
|
|
</div>
|
|
<button type="submit" class="messenger-new-btn" style="width: 100%; margin: 0;">Speichern</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.insertAdjacentHTML('beforeend', formHtml);
|
|
}
|
|
|
|
function closeEditContactForm(event) {
|
|
if (event && event.target !== event.currentTarget) return;
|
|
const form = document.getElementById('edit-contact-form');
|
|
if (form) form.remove();
|
|
}
|
|
|
|
async function updateContact(event, contactId) {
|
|
event.preventDefault();
|
|
const form = event.target;
|
|
const data = {
|
|
name: form.name.value,
|
|
email: form.email.value || null,
|
|
phone: form.phone.value || null,
|
|
student_name: form.student_name.value || null,
|
|
class_name: form.class_name.value || null
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/contacts/${contactId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeEditContactForm();
|
|
loadAdminContacts();
|
|
loadMessengerData();
|
|
} else {
|
|
alert('Kontakt konnte nicht aktualisiert werden.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating contact:', error);
|
|
alert('Fehler beim Aktualisieren.');
|
|
}
|
|
}
|
|
|
|
// Show CSV Import Dialog
|
|
function showCSVImport() {
|
|
const importHtml = `
|
|
<div id="csv-import-form" class="messenger-modal-overlay" onclick="closeCSVImport(event)">
|
|
<div class="messenger-modal" style="max-width: 500px;" onclick="event.stopPropagation()">
|
|
<div class="messenger-modal-header">
|
|
<h3>CSV Import</h3>
|
|
<button onclick="closeCSVImport()" class="messenger-modal-close">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<p style="font-size: 13px; color: var(--bp-text-muted); margin-bottom: 16px;">
|
|
Importieren Sie Kontakte aus einer CSV-Datei. Unterstuetzte Spalten:
|
|
<br><br>
|
|
<code style="background: var(--bp-surface-elevated); padding: 2px 6px; border-radius: 4px;">Name, Kontakt, Email, Telefon, Schueler, Klasse</code>
|
|
</p>
|
|
|
|
<div style="border: 2px dashed var(--bp-border); border-radius: 12px; padding: 40px; text-align: center; margin-bottom: 16px;">
|
|
<input type="file" id="csv-file-input" accept=".csv" style="display: none;" onchange="handleCSVFile(this)">
|
|
<button onclick="document.getElementById('csv-file-input').click()" class="messenger-new-btn" style="margin: 0;">
|
|
📂 CSV-Datei auswaehlen
|
|
</button>
|
|
</div>
|
|
|
|
<div id="csv-import-preview" style="display: none;">
|
|
<p style="font-size: 13px; color: var(--bp-text-muted); margin-bottom: 8px;">Vorschau:</p>
|
|
<div id="csv-preview-content" style="max-height: 200px; overflow-y: auto; background: var(--bp-surface-elevated); border-radius: 8px; padding: 12px; font-size: 12px;"></div>
|
|
<button onclick="submitCSVImport()" class="messenger-new-btn" style="width: 100%; margin: 16px 0 0 0; background: var(--bp-success, #10b981);">
|
|
✓ Importieren
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.insertAdjacentHTML('beforeend', importHtml);
|
|
}
|
|
|
|
function closeCSVImport(event) {
|
|
if (event && event.target !== event.currentTarget) return;
|
|
const form = document.getElementById('csv-import-form');
|
|
if (form) form.remove();
|
|
}
|
|
|
|
let csvFileContent = null;
|
|
|
|
function handleCSVFile(input) {
|
|
const file = input.files?.[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
csvFileContent = e.target.result;
|
|
const lines = csvFileContent.split('\\n').slice(0, 6);
|
|
const preview = lines.map(l => escapeHtml(l)).join('<br>');
|
|
document.getElementById('csv-preview-content').innerHTML = preview;
|
|
document.getElementById('csv-import-preview').style.display = 'block';
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
async function submitCSVImport() {
|
|
if (!csvFileContent) return;
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
const blob = new Blob([csvFileContent], { type: 'text/csv' });
|
|
formData.append('file', blob, 'contacts.csv');
|
|
|
|
const response = await fetch(`${MESSENGER_API}/contacts/import`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
alert(`Import erfolgreich: ${result.imported} Kontakte importiert.`);
|
|
closeCSVImport();
|
|
loadAdminContacts();
|
|
loadMessengerData();
|
|
} else {
|
|
const error = await response.json();
|
|
alert('Import fehlgeschlagen: ' + (error.detail || 'Unbekannter Fehler'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error importing CSV:', error);
|
|
alert('CSV Import fehlgeschlagen.');
|
|
}
|
|
}
|
|
|
|
// Export contacts as CSV
|
|
async function exportContacts() {
|
|
try {
|
|
const response = await fetch(`${MESSENGER_API}/contacts/export/csv`);
|
|
if (response.ok) {
|
|
const blob = await response.blob();
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'kontakte_export.csv';
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
} else {
|
|
alert('Export fehlgeschlagen.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error exporting contacts:', error);
|
|
alert('Export fehlgeschlagen.');
|
|
}
|
|
}
|
|
"""
|