""" 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 """ """ @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 `
${avatar}
${escapeHtml(conv.name)} ${lastTime}
${escapeHtml(conv.last_message || 'Keine Nachrichten')}
${isUnread ? `${unreadCount}` : ''}
`; }).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) => `
${escapeHtml(tpl.name)}
${escapeHtml(tpl.content.substring(0, 50))}...
`).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 = `
Keine Nachrichten
`; } 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 += `
${dateLabel}
`; lastDate = msgDate; } const isSent = msg.sender_id === 'teacher'; // TODO: Get actual user ID const time = formatMessageTime(msg.created_at); html += `
${!isSent ? '
👱
' : ''}
${escapeHtml(msg.content)}
${time} ${isSent && msg.read ? '✓✓' : ''} ${isSent && !msg.read ? '' : ''}
`; }); 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 = `
${!isSent ? '
👱
' : ''}
${escapeHtml(text)}
${time} ${isSent ? '' : ''}
`; 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 = `

Kontakt auswaehlen

${messengerContacts.map(c => `
👱
${escapeHtml(c.name)}
${escapeHtml(c.student_name || '')} ${c.class_name ? '(' + c.class_name + ')' : ''}
`).join('')}
`; 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 = `

Kontakte verwalten

Lade Kontakte...

${messengerContacts.length} Kontakte
`; 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 = `

Keine Kontakte vorhanden.

Fuegen Sie Kontakte hinzu oder importieren Sie eine CSV-Datei.

`; return; } container.innerHTML = messengerContacts.map(c => `
👱
${escapeHtml(c.name)}
${escapeHtml(c.email || '')} ${c.student_name ? ' | Schueler: ' + escapeHtml(c.student_name) : ''} ${c.class_name ? ' (' + c.class_name + ')' : ''}
`).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 = `

Neuer Kontakt

`; 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 = `

Kontakt bearbeiten

`; 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 = `

CSV Import

Importieren Sie Kontakte aus einer CSV-Datei. Unterstuetzte Spalten:

Name, Kontakt, Email, Telefon, Schueler, Klasse

`; 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('
'); 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.'); } } """