// Global variables let currentSchaltungId = null; let uploadedFiles = []; let currentUser = null; // Escape user-controlled values before embedding into innerHTML. Replaces all five // XSS-relevant characters; safe inside both text content and double-quoted attributes. function escapeHtml(value) { if (value === null || value === undefined) return ''; return String(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Initialize the application with background loading document.addEventListener('DOMContentLoaded', async function() { // Verify session before loading anything else; redirect to /login on 401. try { const meRes = await fetch('/api/auth/me', { credentials: 'same-origin' }); if (meRes.status === 401) { window.location.href = '/login'; return; } currentUser = await meRes.json(); renderUserBadge(); } catch (e) { window.location.href = '/login'; return; } loadAnbieter(); loadSchaltungen(); setupFileUpload(); renderWizardAnbieterButtons(); // Set today's date as default const today = new Date().toISOString().split('T')[0]; document.getElementById('eingabeDatum').value = today; document.getElementById('schaltungsDatum').value = today; // Erfasser autofill const erfasser = document.getElementById('wizardErfasser'); if (erfasser) erfasser.value = currentUser.displayName || currentUser.username; document.getElementById('schaltungForm').addEventListener('submit', handleSchaltungSubmit); preloadAllData(); }); // --- Schaltung Wizard --- async function renderWizardAnbieterButtons() { const container = document.getElementById('wizardAnbieterButtons'); if (!container) return; container.innerHTML = '
Lade...
'; try { const anbieter = await apiRequest('/api/anbieter'); container.innerHTML = ''; if (!anbieter.length) { container.innerHTML = '
Keine Anbieter vorhanden. Bitte zuerst unter Administration einen Anbieter anlegen.
'; return; } anbieter.forEach(a => { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'group flex flex-col items-center justify-center p-lg border border-outline-variant rounded bg-surface-container-lowest hover:-translate-y-1 hover:shadow-sm hover:border-primary transition-all duration-200 h-32'; btn.innerHTML = ` cell_tower `; btn.querySelector('span.text-title-sm').textContent = a.name; btn.addEventListener('click', () => wizardSelectAnbieter(a.id, a.name, a.default_vo_id)); container.appendChild(btn); }); } catch (e) { container.innerHTML = '
Fehler beim Laden der Anbieter
'; } } async function wizardSelectAnbieter(anbieterId, anbieterName, defaultVoId) { document.getElementById('anbieter').value = anbieterId; document.getElementById('wizardAnbieterLabel').textContent = ' (' + anbieterName + ')'; document.getElementById('wizardStep1').style.display = 'none'; document.getElementById('wizardStep2').style.display = 'block'; const voSelect = document.getElementById('wizardVo'); voSelect.innerHTML = ''; document.getElementById('wizardTarif').innerHTML = ''; document.getElementById('tarifoptionenSection').style.display = 'none'; try { const vos = await apiRequest(`/api/vos?anbieter_id=${encodeURIComponent(anbieterId)}`); vos.forEach(v => { const label = `${v.nummer}${v.bezeichnung ? ' (' + v.bezeichnung + ')' : ''}`; voSelect.innerHTML += ``; }); if (vos.length === 0) { showMessage('Für diesen Anbieter wurden noch keine VOs angelegt. Bitte in der Administration → VOs eine VO anlegen.', 'error'); return; } // Preselect the provider's default VO if set, otherwise the first VO if it is the only one. let preselect = null; if (defaultVoId && vos.some(v => v.id === defaultVoId)) { preselect = defaultVoId; } else if (vos.length === 1) { preselect = vos[0].id; } if (preselect) { voSelect.value = String(preselect); await loadTarifeForWizard(); } } catch (e) { /* error shown */ } } function wizardBackToStep1() { document.getElementById('wizardStep2').style.display = 'none'; document.getElementById('wizardStep1').style.display = 'block'; } async function loadTarifeForWizard() { const voId = document.getElementById('wizardVo').value; const tarifSelect = document.getElementById('wizardTarif'); tarifSelect.innerHTML = ''; document.getElementById('tarifoptionenSection').style.display = 'none'; if (!voId) return; try { const tarife = await apiRequest(`/api/tarife?vo_id=${encodeURIComponent(voId)}`); tarife.forEach(t => { tarifSelect.innerHTML += ``; }); } catch (e) { /* error shown */ } } async function loadTarifoptionenForWizard() { const tarifId = document.getElementById('wizardTarif').value; const section = document.getElementById('tarifoptionenSection'); const list = document.getElementById('tarifoptionenList'); const noOptions = document.getElementById('noTarifoptionen'); list.innerHTML = ''; noOptions.style.display = 'none'; if (!tarifId) { section.style.display = 'none'; return; } section.style.display = 'flex'; try { const options = await apiRequest(`/api/tarife/${tarifId}/tarifoptionen`); if (!options || options.length === 0) { noOptions.style.display = 'block'; return; } options.forEach(opt => { const wrapper = document.createElement('label'); wrapper.className = 'flex items-start gap-sm p-sm border border-outline-variant rounded-lg bg-surface-container-lowest hover:bg-surface-container-low cursor-pointer'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.name = 'tarifoptionen'; cb.value = opt.id; cb.className = 'mt-0.5 text-primary rounded-sm border-outline-variant focus:ring-primary/20'; wrapper.appendChild(cb); const body = document.createElement('div'); body.className = 'flex flex-col'; const nameEl = document.createElement('span'); nameEl.className = 'text-body-sm text-on-surface font-semibold'; nameEl.textContent = opt.name; body.appendChild(nameEl); if (opt.code) { const codeEl = document.createElement('span'); codeEl.className = 'text-label-caps text-on-surface-variant'; codeEl.textContent = 'Code: ' + opt.code; body.appendChild(codeEl); } if (opt.beschreibung) { const desc = document.createElement('span'); desc.className = 'text-body-sm text-on-surface-variant'; desc.textContent = opt.beschreibung; body.appendChild(desc); } wrapper.appendChild(body); const price = document.createElement('span'); price.className = 'ml-auto font-label-numeric text-label-numeric ' + (opt.provision_euro >= 0 ? 'text-primary' : 'text-error'); price.textContent = (opt.provision_euro >= 0 ? '+' : '') + opt.provision_euro + ' €'; wrapper.appendChild(price); list.appendChild(wrapper); }); } catch (e) { /* error shown */ } } function renderUserBadge() { if (!currentUser) return; // Show admin-only nav items now that we know the role. document.querySelectorAll('.admin-only').forEach(el => { el.style.display = currentUser.isAdmin ? '' : 'none'; }); // Fill the user name in the new top header. const nameEl = document.getElementById('headerUserName'); if (nameEl) nameEl.textContent = currentUser.displayName || currentUser.username; // Wire the header logout button (idempotent: only attach once). const logoutBtn = document.getElementById('headerLogoutBtn'); if (logoutBtn && !logoutBtn.dataset.wired) { logoutBtn.dataset.wired = '1'; logoutBtn.addEventListener('click', async (e) => { e.preventDefault(); try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); } catch (_) {} window.location.href = '/login'; }); } // Remove the legacy floating badge if it was rendered by an older session. const legacy = document.getElementById('userBadge'); if (legacy) legacy.remove(); } // Preload all data in background async function preloadAllData() { console.log('Preloading all data in background...'); try { // Load data for all tabs simultaneously (with delay to not block UI) setTimeout(async () => { const loadPromises = [ loadAdminData(), // Admin data loadProvisionen(), // Provisionen tab loadGeloeschteSchaltungen() // Gelöschte Schaltungen ]; // Execute all loads in parallel await Promise.allSettled(loadPromises); console.log('Background data loading completed'); // Show completion message briefly showMessage('Hintergrunddaten erfolgreich geladen - Tab-Wechsel sind jetzt schneller! ⚡'); // Hide loading indicator if it exists const loadingIndicator = document.getElementById('loadingIndicator'); if (loadingIndicator) { loadingIndicator.style.display = 'none'; } }, 1000); // Load after 1 second to not interfere with initial load } catch (error) { console.error('Error during background loading:', error); } } // Tab switching function showTab(tabName) { // Hide all tabs const tabs = document.querySelectorAll('.tab-content'); tabs.forEach(tab => tab.classList.remove('active')); const navTabs = document.querySelectorAll('.nav-tab'); navTabs.forEach(tab => tab.classList.remove('active')); // Show selected tab document.getElementById(tabName).classList.add('active'); if (event && event.target) { event.target.classList.add('active'); } // Optimized loading - only reload if necessary (data should be preloaded) if (tabName === 'overview') { // Always refresh main data as it changes most frequently loadSchaltungen(); } else if (tabName === 'provisionen') { // Check if data is already loaded, if not load it const provisionenTable = document.querySelector('#provisionenTable tbody'); if (!provisionenTable || !provisionenTable.innerHTML.trim()) { loadProvisionen(); } } else if (tabName === 'geloeschte') { // Check if data is already loaded, if not load it if (!document.getElementById('geloeschteList') || !document.getElementById('geloeschteList').innerHTML.trim()) { loadGeloeschteSchaltungen(); } } else if (tabName === 'admin') { // Check if admin data is already loaded if (!document.getElementById('anbieterList') || !document.getElementById('anbieterList').innerHTML.trim()) { loadAdminData(); } } else if (tabName === 'users') { loadUsers(); } } // API Helper functions async function apiRequest(url, options = {}) { try { const opts = Object.assign({ credentials: 'same-origin' }, options); const response = await fetch(url, opts); // Session expired or unauthenticated -> back to login if (response.status === 401) { window.location.href = '/login'; throw new Error('Nicht angemeldet'); } const contentType = response.headers.get('content-type'); let data = null; if (contentType && contentType.includes('application/json')) { const text = await response.text(); if (text.trim()) { data = JSON.parse(text); } } if (!response.ok) { throw new Error(data?.error || `HTTP ${response.status}: ${response.statusText}`); } return data; } catch (error) { showMessage('Fehler: ' + error.message, 'error'); throw error; } } // Message display as overlay function showMessage(message, type = 'success') { // Create overlay container if it doesn't exist let overlay = document.getElementById('message-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'message-overlay'; overlay.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10000; pointer-events: none; max-width: 400px; `; document.body.appendChild(overlay); } // Create message element const messageDiv = document.createElement('div'); messageDiv.className = `message-toast ${type}`; messageDiv.textContent = message; messageDiv.style.cssText = ` background: ${type === 'success' ? '#d4edda' : '#f8d7da'}; border: 1px solid ${type === 'success' ? '#c3e6cb' : '#f5c6cb'}; color: ${type === 'success' ? '#155724' : '#721c24'}; padding: 12px 16px; margin-bottom: 10px; border-radius: 6px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); pointer-events: auto; animation: slideInRight 0.3s ease-out; font-family: Arial, sans-serif; font-size: 14px; line-height: 1.4; cursor: pointer; transition: opacity 0.3s ease-out; `; // Add CSS animation if not already added if (!document.getElementById('message-animations')) { const style = document.createElement('style'); style.id = 'message-animations'; style.textContent = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } `; document.head.appendChild(style); } // Click to dismiss messageDiv.addEventListener('click', () => { messageDiv.style.animation = 'slideOutRight 0.3s ease-out'; setTimeout(() => messageDiv.remove(), 300); }); overlay.appendChild(messageDiv); // Auto-remove after 3 seconds setTimeout(() => { if (messageDiv.parentNode) { messageDiv.style.animation = 'slideOutRight 0.3s ease-out'; setTimeout(() => messageDiv.remove(), 300); } }, 3000); } // Load Anbieter async function loadAnbieter() { try { const anbieter = await apiRequest('/api/anbieter'); // Note: #anbieter is now a hidden input populated by the wizard; // we keep the helper only for selects that still exist (admin form). const tarifAnbieterSelect = document.getElementById('tarifAnbieter'); if (tarifAnbieterSelect) { tarifAnbieterSelect.innerHTML = ''; anbieter.forEach(a => { tarifAnbieterSelect.innerHTML += ``; }); } // Refresh wizard buttons in case anbieters changed renderWizardAnbieterButtons(); } catch (error) { console.error('Fehler beim Laden der Anbieter:', error); } } // Load Tarife for selected Anbieter async function loadTarife() { const anbieterId = document.getElementById('anbieter').value; const tarifSelect = document.getElementById('tarif'); // Hide tarifoptionen section when anbieter changes hideTarifoptionen(); if (!anbieterId) { tarifSelect.innerHTML = ''; return; } try { const tarife = await apiRequest(`/api/tarife/${anbieterId}`); tarifSelect.innerHTML = ''; tarife.forEach(t => { tarifSelect.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Tarife:', error); tarifSelect.innerHTML = ''; } } // Load tarifoptionen when tarif is selected async function loadTarifoptionen() { const tarifSelect = document.getElementById('tarif'); const tarifId = tarifSelect.value; if (!tarifId) { hideTarifoptionen(); return; } try { const tarifoptionen = await apiRequest(`/api/tarife/${tarifId}/tarifoptionen`); displayTarifoptionen(tarifoptionen); } catch (error) { console.error('Fehler beim Laden der Tarifoptionen:', error); hideTarifoptionen(); } } function displayTarifoptionen(tarifoptionen) { const section = document.getElementById('tarifoptionenSection'); const list = document.getElementById('tarifoptionenList'); const noOptions = document.getElementById('noTarifoptionen'); // Clear existing options list.innerHTML = ''; if (tarifoptionen.length === 0) { section.style.display = 'block'; list.style.display = 'none'; noOptions.style.display = 'block'; } else { section.style.display = 'block'; list.style.display = 'grid'; noOptions.style.display = 'none'; tarifoptionen.forEach(option => { const optionDiv = document.createElement('div'); optionDiv.style.cssText = 'padding: 10px; border: 1px solid #ddd; border-radius: 5px; background: #f9f9f9;'; optionDiv.innerHTML = ` `; list.appendChild(optionDiv); }); } } function hideTarifoptionen() { const section = document.getElementById('tarifoptionenSection'); section.style.display = 'none'; } // Handle Schaltung form submission async function handleSchaltungSubmit(e) { e.preventDefault(); const formData = new FormData(e.target); const schaltungData = Object.fromEntries(formData.entries()); // Wizard fields not part of FormData (hidden input has it but vo is on a non-named select). schaltungData.vo_id = document.getElementById('wizardVo').value || null; const selectedTarifoptionen = Array.from(document.querySelectorAll('input[name="tarifoptionen"]:checked')) .map(checkbox => parseInt(checkbox.value)); if (selectedTarifoptionen.length) { schaltungData.tarifoptionen = selectedTarifoptionen; } if (schaltungData.hardware && schaltungData.hardware.length !== 15) { showMessage('Hardware muss genau 15 Zeichen haben (oder leer lassen)', 'error'); return; } try { const response = await apiRequest('/api/schaltungen', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(schaltungData), }); currentSchaltungId = response.id; if (uploadedFiles.length > 0) { await uploadDocuments(currentSchaltungId); } showMessage('Schaltung erfolgreich gespeichert!'); e.target.reset(); uploadedFiles = []; document.getElementById('uploadedFiles').innerHTML = ''; hideTarifoptionen(); const today = new Date().toISOString().split('T')[0]; document.getElementById('eingabeDatum').value = today; document.getElementById('schaltungsDatum').value = today; // Erfasser wieder befüllen (reset löscht's nicht da readonly, aber safety) const erfasser = document.getElementById('wizardErfasser'); if (erfasser) erfasser.value = currentUser.displayName || currentUser.username; // Zurück zu Schritt 1 für die nächste Schaltung wizardBackToStep1(); await refreshDataAfterSchaltungChange(); } catch (error) { console.error('Fehler beim Speichern der Schaltung:', error); } } // Add tarifoptionen to schaltung async function addTarifoptionenToSchaltung(schaltungId, tarifoptionenIds) { for (const tarifoptionId of tarifoptionenIds) { try { await apiRequest(`/api/schaltungen/${schaltungId}/tarifoptionen`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tarifoption_id: tarifoptionId }) }); } catch (error) { console.warn(`Fehler beim Hinzufügen der Tarifoption ${tarifoptionId}:`, error); } } } // File upload setup function setupFileUpload() { const uploadArea = document.getElementById('fileUploadArea'); const fileInput = document.getElementById('documentUpload'); uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); handleFiles(e.dataTransfer.files); }); fileInput.addEventListener('change', (e) => { handleFiles(e.target.files); }); } function handleFiles(files) { Array.from(files).forEach(file => { if (file.size > 10 * 1024 * 1024) { showMessage(`Datei ${file.name} ist zu groß (max. 10MB)`, 'error'); return; } uploadedFiles.push(file); displayUploadedFile(file); }); } function displayUploadedFile(file) { const filesDiv = document.getElementById('uploadedFiles'); const fileDiv = document.createElement('div'); fileDiv.className = 'document-item'; const span = document.createElement('span'); span.textContent = `${file.name} (${formatFileSize(file.size)})`; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn-danger'; btn.textContent = 'Entfernen'; btn.addEventListener('click', () => removeFile(file.name)); fileDiv.appendChild(span); fileDiv.appendChild(btn); filesDiv.appendChild(fileDiv); } function removeFile(fileName) { uploadedFiles = uploadedFiles.filter(file => file.name !== fileName); const filesDiv = document.getElementById('uploadedFiles'); const fileItems = filesDiv.querySelectorAll('.document-item'); fileItems.forEach(item => { if (item.querySelector('span').textContent.startsWith(fileName)) { item.remove(); } }); } function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } async function uploadDocuments(schaltungId) { for (const file of uploadedFiles) { const formData = new FormData(); formData.append('document', file); try { await fetch(`/api/schaltungen/${schaltungId}/dokumente`, { method: 'POST', body: formData }); } catch (error) { showMessage(`Fehler beim Hochladen von ${file.name}`, 'error'); } } } // Refresh all related data when schaltungen change async function refreshDataAfterSchaltungChange() { // Always refresh schaltungen await loadSchaltungen(); // Only refresh provisions if the tab has been accessed before (performance optimization) const provisionenTable = document.querySelector('#provisionenTable tbody'); if (provisionenTable && provisionenTable.innerHTML.trim()) { await loadProvisionen(); } } // Load Schaltungen overview async function loadSchaltungen() { try { const schaltungen = await apiRequest('/api/schaltungen'); renderSchaltungenRows(schaltungen); } catch (error) { console.error('Fehler beim Laden der Schaltungen:', error); } } // Render schaltungen rows in the Stitch overview table. function renderSchaltungenRows(schaltungen) { const tbody = document.querySelector('#schaltungenTable tbody'); if (!tbody) return; tbody.innerHTML = ''; if (!schaltungen || !schaltungen.length) { const tr = document.createElement('tr'); tr.innerHTML = 'Keine Schaltungen vorhanden'; tbody.appendChild(tr); return; } schaltungen.forEach(s => { const tr = document.createElement('tr'); tr.className = 'border-b border-outline-variant/30 even:bg-surface-container-low/50 hover:bg-surface-variant/30 transition-colors'; tr.innerHTML = ` ${escapeHtml(s.schaltungs_id)}
E: ${escapeHtml(formatDate(s.eingabe_datum))} A: ${escapeHtml(formatDate(s.schaltungs_datum))}
${escapeHtml((s.vorname || '') + ' ' + (s.nachname || ''))}
${escapeHtml(s.firma || '–')}
${escapeHtml(s.rufnummer || '–')} ${escapeHtml(s.anbieter_name)} ${escapeHtml(s.tarif_name)} ${escapeHtml(s.hardware || '–')}
`; tr.querySelectorAll('button[data-action]').forEach(btn => { btn.addEventListener('click', () => { const action = btn.dataset.action; const id = parseInt(btn.dataset.id, 10); if (action === 'documents') showDocuments(id); else if (action === 'edit') editSchaltung(id); else if (action === 'delete') deleteSchaltung(id, btn.dataset.schaltungsId, btn.dataset.name); }); }); tbody.appendChild(tr); }); } // Format any ISO-like date string (YYYY-MM-DD or full timestamp) as DD.MM.YYYY // with leading zeros. Returns the original string unchanged if it cannot be parsed. function formatDate(dateString) { if (!dateString) return ''; const date = new Date(dateString); if (isNaN(date.getTime())) return String(dateString); const dd = String(date.getDate()).padStart(2, '0'); const mm = String(date.getMonth() + 1).padStart(2, '0'); const yyyy = date.getFullYear(); return `${dd}.${mm}.${yyyy}`; } async function showDocuments(schaltungId) { try { const documents = await apiRequest(`/api/schaltungen/${schaltungId}/dokumente`); const modal = document.getElementById('documentsModal'); const modalBody = document.getElementById('documentsModalBody'); modalBody.innerHTML = ''; if (documents.length === 0) { const empty = document.createElement('div'); empty.className = 'flex flex-col items-center justify-center py-xl text-center gap-sm'; empty.innerHTML = ` draft
Keine Dokumente vorhanden
Für diese Schaltung wurden noch keine Dokumente hochgeladen.
`; modalBody.appendChild(empty); } else { documents.forEach(doc => { const fileExt = doc.original_filename.split('.').pop().toLowerCase(); let iconName = 'description'; if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) iconName = 'image'; else if (['doc', 'docx'].includes(fileExt)) iconName = 'article'; const fileSize = doc.filesize ? `${(doc.filesize / 1024).toFixed(1)} KB` : 'Unbekannt'; const uploadDate = doc.uploaded_at ? formatDate(doc.uploaded_at) : 'Unbekannt'; const showView = fileExt === 'pdf'; const safeName = escapeHtml(doc.original_filename); const card = document.createElement('div'); card.className = 'flex items-center gap-md p-md border border-outline-variant rounded-lg bg-surface-container-lowest hover:bg-surface-container-low transition-colors'; card.innerHTML = ` ${iconName}
${safeName} ${escapeHtml(fileSize)} · Hochgeladen: ${escapeHtml(uploadDate)}
${showView ? `` : ''}
`; card.querySelectorAll('button[data-doc-action]').forEach(btn => { btn.addEventListener('click', () => { const action = btn.dataset.docAction; const id = parseInt(btn.dataset.id, 10); if (action === 'view') viewPdfDocument(id, btn.dataset.name); else if (action === 'download') downloadDocument(id); else if (action === 'delete') deleteDocument(id, btn.dataset.name, parseInt(btn.dataset.schaltungId, 10)); }); }); modalBody.appendChild(card); }); } const fileInput = document.getElementById('additionalDocumentUpload'); fileInput.onchange = function (event) { uploadAdditionalDocuments(schaltungId, event.target.files); }; modal.classList.remove('hidden'); modal.classList.add('flex'); } catch (error) { console.error('Fehler beim Laden der Dokumente:', error); showMessage('Fehler beim Laden der Dokumente', 'error'); } } function closeDocumentsModal() { const modal = document.getElementById('documentsModal'); modal.classList.add('hidden'); modal.classList.remove('flex'); } function downloadDocument(documentId) { window.open(`/api/dokumente/${documentId}/download`, '_blank'); } // Upload additional documents async function uploadAdditionalDocuments(schaltungId, files) { if (!files || files.length === 0) return; const uploadPromises = Array.from(files).map(async (file) => { const formData = new FormData(); formData.append('document', file); try { const response = await fetch(`/api/schaltungen/${schaltungId}/dokumente`, { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); return { success: true, filename: result.filename }; } catch (error) { console.error(`Fehler beim Hochladen von ${file.name}:`, error); return { success: false, filename: file.name, error: error.message }; } }); try { const results = await Promise.all(uploadPromises); const successCount = results.filter(r => r.success).length; const failureCount = results.filter(r => !r.success).length; if (successCount > 0) { showMessage(`${successCount} Dokument(e) erfolgreich hochgeladen`, 'success'); // Refresh the documents display showDocuments(schaltungId); } if (failureCount > 0) { showMessage(`${failureCount} Dokument(e) konnten nicht hochgeladen werden`, 'error'); } // Clear the file input document.getElementById('additionalDocumentUpload').value = ''; } catch (error) { console.error('Fehler beim Upload:', error); showMessage('Fehler beim Hochladen der Dokumente', 'error'); } } // Delete document async function deleteDocument(documentId, filename, schaltungId) { if (!confirm(`Möchten Sie das Dokument "${filename}" wirklich löschen?`)) { return; } try { const response = await fetch(`/api/dokumente/${documentId}`, { method: 'DELETE' }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); showMessage('Dokument erfolgreich gelöscht', 'success'); // Refresh the documents display showDocuments(schaltungId); } catch (error) { console.error('Fehler beim Löschen:', error); showMessage('Fehler beim Löschen des Dokuments', 'error'); } } // Close modal when clicking outside document.addEventListener('click', function(event) { const modal = document.getElementById('documentsModal'); if (event.target === modal) { closeDocumentsModal(); } }); // Admin functions // New Admin Category Navigation function showAdminCategory(category) { // Hide all categories document.querySelectorAll('.admin-category').forEach(cat => { cat.classList.remove('active'); }); // Remove active class from all buttons document.querySelectorAll('.admin-cat-btn').forEach(btn => { btn.classList.remove('active'); }); // Show selected category document.getElementById(`admin-${category}`).classList.add('active'); // Add active class to clicked button event.target.closest('.admin-cat-btn').classList.add('active'); // Load data for the category switch(category) { case 'anbieter': loadAnbieterAdmin(); break; case 'vos': loadVosAdmin(); loadAnbieterForVoForm(); break; case 'tarife': loadTarifeAdmin(); loadAnbieterForTarife(); break; case 'tarifoptionen': loadTarifoptionAnbieter(); displayTarifoptionenAdmin(); break; case 'mitarbeiter-prozentsaetze': loadMitarbeiterAnbieter(); loadMitarbeiterProzentsaetzeAdmin(); break; case 'mitarbeiter-provisionen': loadTarifProvisionsAdmin(); break; } } async function loadAnbieterAdmin() { try { const anbieter = await apiRequest('/api/anbieter'); const tbody = document.getElementById('anbieterList'); tbody.innerHTML = ''; if (anbieter.length === 0) { tbody.innerHTML = 'Keine Anbieter vorhanden'; return; } anbieter.forEach(a => { const createdAt = formatDate(a.created_at); const tr = document.createElement('tr'); tr.innerHTML = ` ${a.id} ${escapeHtml(a.name)} ${escapeHtml(createdAt)} `; tr.querySelector('button[data-anbieter-action="delete"]').addEventListener('click', (e) => { const btn = e.currentTarget; deleteAnbieter(parseInt(btn.dataset.id, 10), btn.dataset.name); }); tbody.appendChild(tr); }); } catch (error) { console.error('Fehler beim Laden der Anbieter-Administration:', error); const tbody = document.getElementById('anbieterList'); tbody.innerHTML = 'Fehler beim Laden der Daten'; } } // Prevent race conditions let isLoadingTarifeAdmin = false; async function loadTarifeAdmin() { if (isLoadingTarifeAdmin) { console.warn('loadTarifeAdmin already running, skipping to prevent race condition'); return; } try { isLoadingTarifeAdmin = true; // Use new API endpoint that gets all tarife with anbieter names const tarife = await apiRequest('/api/tarife'); const tbody = document.getElementById('tarifeList'); tbody.innerHTML = ''; if (tarife.length === 0) { tbody.innerHTML = 'Keine Tarife vorhanden'; return; } tarife.forEach(t => { const provision = `${(t.provision_euro || 0).toFixed(2).replace('.', ',')}€`; const voLabel = t.vo_nummer ? `${t.vo_nummer}${t.vo_bezeichnung ? ' (' + t.vo_bezeichnung + ')' : ''}` : '—'; const tr = document.createElement('tr'); tr.id = `tarif-item-${t.id}`; tr.innerHTML = ` ${t.id} ${escapeHtml(t.anbieter_name || 'Unbekannt')} ${escapeHtml(voLabel)} ${escapeHtml(t.name)} ${escapeHtml(provision)} ${escapeHtml(t.beschreibung || '-')} `; tr.querySelectorAll('button[data-tarif-action]').forEach(btn => { btn.addEventListener('click', () => { const action = btn.dataset.tarifAction; const id = parseInt(btn.dataset.id, 10); if (action === 'edit') editTariff(id); else if (action === 'delete') deleteTariff(id, btn.dataset.name); }); }); tbody.appendChild(tr); }); } catch (error) { console.error('Fehler beim Laden der Tarif-Administration:', error); const tbody = document.getElementById('tarifeList'); tbody.innerHTML = 'Fehler beim Laden der Daten'; } finally { isLoadingTarifeAdmin = false; } } async function addAnbieter() { const name = document.getElementById('newAnbieter').value.trim(); if (!name) { showMessage('Bitte einen Anbieter-Namen eingeben', 'error'); return; } try { await apiRequest('/api/anbieter', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }); document.getElementById('newAnbieter').value = ''; showMessage('Anbieter erfolgreich hinzugefügt'); // Reload data await loadAnbieter(); await loadAnbieterAdmin(); } catch (error) { console.error('Fehler beim Hinzufügen des Anbieters:', error); } } // Load anbieter for tarife select async function loadAnbieterForTarife() { try { const anbieter = await apiRequest('/api/anbieter'); const select = document.getElementById('tarifAnbieter'); select.innerHTML = ''; anbieter.forEach(a => { select.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter für Tarife:', error); } } // Load anbieter for mitarbeiter select async function loadMitarbeiterAnbieter() { try { const anbieter = await apiRequest('/api/anbieter'); const select = document.getElementById('mitarbeiterAnbieter'); select.innerHTML = ''; anbieter.forEach(a => { select.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter für Mitarbeiter:', error); } } // Load anbieter for tarifoption select async function loadTarifoptionAnbieter() { try { const anbieter = await apiRequest('/api/anbieter'); const select = document.getElementById('tarifoptionAnbieter'); select.innerHTML = ''; anbieter.forEach(a => { select.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter für Tarifoptionen:', error); } } async function addTarif() { const name = document.getElementById('newTarif').value.trim(); const anbieterId = document.getElementById('tarifAnbieter').value; const voId = document.getElementById('tarifVo').value; const provisionEuro = document.getElementById('tarifProvisionBetrag').value; const beschreibung = document.getElementById('tarifBeschreibung').value.trim(); if (!name || !anbieterId) { showMessage('Bitte Tarif-Name und Anbieter auswählen', 'error'); return; } if (!voId) { showMessage('Bitte VO auswählen', 'error'); return; } if (provisionEuro && parseFloat(provisionEuro) < 0) { showMessage('Provision kann nicht negativ sein', 'error'); return; } try { const requestData = { name, anbieter_id: anbieterId, vo_id: voId, provision_euro: provisionEuro ? parseFloat(provisionEuro) : 0, beschreibung: beschreibung || null, }; await apiRequest('/api/tarife', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData), }); document.getElementById('newTarif').value = ''; document.getElementById('tarifAnbieter').value = ''; document.getElementById('tarifVo').innerHTML = ''; document.getElementById('tarifProvisionBetrag').value = ''; document.getElementById('tarifBeschreibung').value = ''; showMessage('Tarif erfolgreich hinzugefügt'); await loadTarifeAdmin(); await loadTarifeForProvision(); } catch (error) { console.error('Fehler beim Hinzufügen des Tarifs:', error); } } // Populates the VO dropdown in the add-tariff form based on the selected provider. async function loadVosForTarifForm() { const anbieterId = document.getElementById('tarifAnbieter').value; const select = document.getElementById('tarifVo'); select.innerHTML = ''; if (!anbieterId) return; try { const vos = await apiRequest(`/api/vos?anbieter_id=${encodeURIComponent(anbieterId)}`); vos.forEach(v => { const label = `${v.nummer}${v.bezeichnung ? ' (' + v.bezeichnung + ')' : ''}`; select.innerHTML += ``; }); } catch (e) { /* error already shown */ } } // Commission management functions async function loadProvisionen() { try { const data = await apiRequest('/api/provisionen'); // Store data globally for filtering allProvisionen = data; // Update filter dropdowns await updateFilterDropdowns(); // Apply current filters (which will also update the display) applyFiltersProvisionen(); } catch (error) { console.error('Fehler beim Laden der Provisionen:', error); } } async function loadAdminData() { await loadAnbieterAdmin(); await loadTarifeAdmin(); await loadGlobalProvisionsregelnAdmin(); await loadProvisionsregelnAdmin(); await loadTarifeForProvision(); await loadGlobalProvisionAnbieter(); await loadTarifoptionenAdmin(); } async function loadTarifeForProvision() { try { const anbieter = await apiRequest('/api/anbieter'); const provisionTarifSelect = document.getElementById('provisionTarif'); if (!provisionTarifSelect) return; provisionTarifSelect.innerHTML = ''; for (const a of anbieter) { const tarife = await apiRequest(`/api/tarife/${a.id}`); if (tarife.length > 0) { tarife.forEach(t => { provisionTarifSelect.innerHTML += ``; }); } } } catch (error) { console.error('Fehler beim Laden der Tarife für Provisionen:', error); } } async function loadProvisionsregelnAdmin() { try { const anbieter = await apiRequest('/api/anbieter'); const provisionsregelnList = document.getElementById('provisionsregelnList'); if (!provisionsregelnList) return; provisionsregelnList.innerHTML = ''; for (const a of anbieter) { const tarife = await apiRequest(`/api/tarife/${a.id}`); for (const t of tarife) { const regeln = await apiRequest(`/api/tarife/${t.id}/provisionsregeln`); if (regeln.length > 0) { const providerDiv = document.createElement('div'); const h4 = document.createElement('h4'); h4.textContent = `${a.name} - ${t.name}`; providerDiv.appendChild(h4); provisionsregelnList.appendChild(providerDiv); regeln.forEach(r => { const item = document.createElement('div'); item.className = 'list-item'; item.style.marginLeft = '20px'; const typText = r.provision_typ === 'fix' ? `${r.betrag} €` : `${r.betrag} %`; const statusText = r.aktiv ? 'Aktiv' : 'Inaktiv'; item.innerHTML = ` ${escapeHtml(typText)} - ${escapeHtml(r.beschreibung || 'Keine Beschreibung')} (${escapeHtml(statusText)})
`; item.querySelector('button[data-regel-action]').addEventListener('click', (e) => { const btn = e.currentTarget; toggleProvisionsregel(parseInt(btn.dataset.id, 10), btn.dataset.aktiv === 'true'); }); provisionsregelnList.appendChild(item); }); } } } } catch (error) { console.error('Fehler beim Laden der Provisionsregeln-Administration:', error); } } async function addProvisionsregel() { const tarifId = document.getElementById('provisionTarif').value; const typ = document.getElementById('provisionTyp').value; const betrag = document.getElementById('provisionBetrag').value; const beschreibung = document.getElementById('provisionBeschreibung').value; if (!tarifId || !typ || !betrag) { showMessage('Bitte alle Pflichtfelder ausfüllen', 'error'); return; } if (typ === 'prozent' && (parseFloat(betrag) < 0 || parseFloat(betrag) > 100)) { showMessage('Prozentual muss zwischen 0 und 100 sein', 'error'); return; } if (parseFloat(betrag) < 0) { showMessage('Betrag kann nicht negativ sein', 'error'); return; } try { await apiRequest('/api/provisionsregeln', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tarif_id: tarifId, provision_typ: typ, betrag: parseFloat(betrag), beschreibung: beschreibung || null }) }); // Reset form document.getElementById('provisionTarif').value = ''; document.getElementById('provisionTyp').value = ''; document.getElementById('provisionBetrag').value = ''; document.getElementById('provisionBeschreibung').value = ''; showMessage('Provisionsregel erfolgreich hinzugefügt'); // Reload data await loadProvisionsregelnAdmin(); } catch (error) { console.error('Fehler beim Hinzufügen der Provisionsregel:', error); } } async function toggleProvisionsregel(id, aktiv) { try { await apiRequest(`/api/provisionsregeln/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ aktiv: aktiv }) }); showMessage(`Provisionsregel erfolgreich ${aktiv ? 'aktiviert' : 'deaktiviert'}`); // Reload data await loadProvisionsregelnAdmin(); } catch (error) { console.error('Fehler beim Ändern der Provisionsregel:', error); showMessage('Fehler beim Ändern der Provisionsregel', 'error'); } } // Global Provider Commission Rules Functions async function loadGlobalProvisionAnbieter() { try { const anbieter = await apiRequest('/api/anbieter'); const globalProvisionAnbieterSelect = document.getElementById('globalProvisionAnbieter'); if (!globalProvisionAnbieterSelect) return; globalProvisionAnbieterSelect.innerHTML = ''; anbieter.forEach(a => { globalProvisionAnbieterSelect.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter für globale Provisionen:', error); } } async function loadGlobalProvisionsregelnAdmin() { try { const anbieter = await apiRequest('/api/anbieter'); const globalProvisionsregelnList = document.getElementById('globalProvisionsregelnList'); if (!globalProvisionsregelnList) return; globalProvisionsregelnList.innerHTML = ''; for (const a of anbieter) { const regeln = await apiRequest(`/api/anbieter/${a.id}/provisionsregeln`); if (regeln.length > 0) { const providerDiv = document.createElement('div'); const h4 = document.createElement('h4'); h4.textContent = `Globale Regeln für ${a.name}`; providerDiv.appendChild(h4); globalProvisionsregelnList.appendChild(providerDiv); regeln.forEach(r => { const item = document.createElement('div'); item.className = 'list-item'; item.style.marginLeft = '20px'; let displayText = 'Keine Mitarbeiter-Provision definiert'; if (r.verkaeufer_provision_typ && r.verkaeufer_provision_betrag) { const mitarbeiterText = r.verkaeufer_provision_typ === 'fix' ? `${r.verkaeufer_provision_betrag} €` : `${r.verkaeufer_provision_betrag} %`; displayText = `Mitarbeiter: ${mitarbeiterText}`; if (r.verkaeufer_provision_beschreibung) { displayText += ` - ${r.verkaeufer_provision_beschreibung}`; } } const statusText = r.aktiv ? 'Aktiv' : 'Inaktiv'; item.innerHTML = ` ${escapeHtml(displayText)} (${escapeHtml(statusText)})
`; item.querySelector('button[data-global-action]').addEventListener('click', (e) => { const btn = e.currentTarget; toggleGlobalProvisionsregel(parseInt(btn.dataset.id, 10), btn.dataset.aktiv === 'true'); }); globalProvisionsregelnList.appendChild(item); }); } } } catch (error) { console.error('Fehler beim Laden der globalen Provisionsregeln-Administration:', error); } } async function addGlobalProvisionsregel() { const anbieterId = document.getElementById('globalProvisionAnbieter').value; // Employee provision fields const verkaeuferProvisionTyp = document.getElementById('globalVerkaeuferProvisionTyp').value; const verkaeuferProvisionBetrag = document.getElementById('globalVerkaeuferProvisionBetrag').value; const verkaeuferProvisionBeschreibung = document.getElementById('globalVerkaeuferProvisionBeschreibung').value; if (!anbieterId || !verkaeuferProvisionTyp || !verkaeuferProvisionBetrag) { showMessage('Bitte alle Pflichtfelder ausfüllen', 'error'); return; } if (verkaeuferProvisionTyp === 'prozent' && (parseFloat(verkaeuferProvisionBetrag) < 0 || parseFloat(verkaeuferProvisionBetrag) > 100)) { showMessage('Mitarbeiter-Provision Prozentual muss zwischen 0 und 100 sein', 'error'); return; } if (parseFloat(verkaeuferProvisionBetrag) < 0) { showMessage('Mitarbeiter-Provision kann nicht negativ sein', 'error'); return; } try { const requestData = { anbieter_id: anbieterId, // For employee-only rules, we need a meaningful shop provision as base provision_typ: 'prozent', // Use percentage as it's most flexible betrag: 100, // 100% - this means shop gets the full tariff commission beschreibung: 'Standard Anbieter-Provision (100% der Tarif-Provision)', verkaeufer_provision_typ: verkaeuferProvisionTyp, verkaeufer_provision_betrag: parseFloat(verkaeuferProvisionBetrag), verkaeufer_provision_beschreibung: verkaeuferProvisionBeschreibung || null }; await apiRequest('/api/anbieter-provisionsregeln', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }); // Reset form document.getElementById('globalProvisionAnbieter').value = ''; document.getElementById('globalVerkaeuferProvisionTyp').value = ''; document.getElementById('globalVerkaeuferProvisionBetrag').value = ''; document.getElementById('globalVerkaeuferProvisionBeschreibung').value = ''; showMessage('Globale Mitarbeiter-Provisionsregel erfolgreich hinzugefügt'); // Reload data await loadGlobalProvisionsregelnAdmin(); } catch (error) { console.error('Fehler beim Hinzufügen der globalen Provisionsregel:', error); } } async function toggleGlobalProvisionsregel(id, aktiv) { try { await apiRequest(`/api/anbieter-provisionsregeln/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ aktiv: aktiv }) }); showMessage(`Globale Provisionsregel erfolgreich ${aktiv ? 'aktiviert' : 'deaktiviert'}`); // Reload data await loadGlobalProvisionsregelnAdmin(); } catch (error) { console.error('Fehler beim Ändern der globalen Provisionsregel:', error); showMessage('Fehler beim Ändern der globalen Provisionsregel', 'error'); } } // Edit/Delete Functions for Tariffs and Schaltungen // Modal Management function closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; } // Tariff Edit/Delete Functions async function editTariff(id) { try { const tariff = await apiRequest(`/api/tarif/${id}`); if (!tariff) { showMessage('Tarif nicht gefunden', 'error'); return; } document.getElementById('editTariffId').value = tariff.id; document.getElementById('editTariffName').value = tariff.name; document.getElementById('editTariffProvisionBetrag').value = tariff.provision_euro || ''; // Populate VO dropdown with the tariff's provider VOs and preselect. const voSelect = document.getElementById('editTariffVo'); voSelect.innerHTML = ''; const vos = await apiRequest(`/api/vos?anbieter_id=${tariff.anbieter_id}`); vos.forEach(v => { const label = `${v.nummer}${v.bezeichnung ? ' (' + v.bezeichnung + ')' : ''}`; const opt = document.createElement('option'); opt.value = v.id; opt.textContent = label; if (tariff.vo_id == v.id) opt.selected = true; voSelect.appendChild(opt); }); // Populate tarifoptionen checkbox grid (anbieter-wide options, with assigned flag) const container = document.getElementById('editTariffOptionenContainer'); container.innerHTML = ''; const options = await apiRequest(`/api/tarife/${id}/tarifoptionen-multi`); if (!options || options.length === 0) { container.innerHTML = 'Keine Tarifoptionen für diesen Anbieter vorhanden'; } else { options.forEach(o => { const wrapper = document.createElement('label'); wrapper.style.cssText = 'display:flex; align-items:center; gap:8px; padding:4px 0; cursor:pointer;'; const cb = document.createElement('input'); cb.type = 'checkbox'; cb.value = o.id; cb.checked = !!o.assigned; cb.className = 'edit-tariff-option-checkbox'; wrapper.appendChild(cb); const span = document.createElement('span'); const price = (o.provision_euro >= 0 ? '+' : '') + o.provision_euro + '€'; span.textContent = `${o.name}${o.code ? ' [' + o.code + ']' : ''} (${price})`; wrapper.appendChild(span); container.appendChild(wrapper); }); } document.getElementById('editTariffModal').style.display = 'block'; document.getElementById('editTariffForm').onsubmit = async (e) => { e.preventDefault(); const checked = Array.from(container.querySelectorAll('.edit-tariff-option-checkbox:checked')) .map(cb => parseInt(cb.value, 10)); const formData = { name: document.getElementById('editTariffName').value.trim(), vo_id: document.getElementById('editTariffVo').value || null, provision_euro: document.getElementById('editTariffProvisionBetrag').value ? parseFloat(document.getElementById('editTariffProvisionBetrag').value) : 0, tarifoption_ids: checked, }; try { await apiRequest(`/api/tarife/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); closeModal('editTariffModal'); showMessage('Tarif erfolgreich aktualisiert'); await loadTarifeAdmin(); await loadTarifeForProvision(); } catch (error) { console.error('Fehler beim Aktualisieren des Tarifs:', error); } }; } catch (error) { console.error('Fehler beim Laden der Tarif-Daten:', error); } } async function deleteTariff(id, name) { if (!confirm(`Sind Sie sicher, dass Sie den Tarif "${name}" löschen möchten?\n\nHinweis: Tarife mit bestehenden Schaltungen können nicht gelöscht werden.`)) { return; } try { await apiRequest(`/api/tarife/${id}`, { method: 'DELETE' }); showMessage('Tarif erfolgreich gelöscht'); // Remove the deleted item from DOM immediately (much faster) const deletedElement = document.getElementById(`tarif-item-${id}`); if (deletedElement) { deletedElement.remove(); } } catch (error) { console.error('Fehler beim Löschen des Tarifs:', error); } } async function deleteAnbieter(id, name) { if (!confirm(`Sind Sie sicher, dass Sie den Anbieter "${name}" löschen möchten?\n\nHinweis: Anbieter mit bestehenden Tarifen oder Schaltungen können nicht gelöscht werden.`)) { return; } try { await apiRequest(`/api/anbieter/${id}`, { method: 'DELETE' }); showMessage('Anbieter erfolgreich gelöscht'); await loadAnbieterAdmin(); await loadAnbieter(); await loadAnbieterForProvision(); } catch (error) { console.error('Fehler beim Löschen des Anbieters:', error); // Show more detailed error message if provided by server if (error.message) { showMessage(error.message, 'error'); } } } // Schaltung Edit/Delete Functions async function editSchaltung(id) { try { // Get schaltung data const schaltungen = await apiRequest('/api/schaltungen'); const schaltung = schaltungen.find(s => s.id == id); if (!schaltung) { showMessage('Schaltung nicht gefunden', 'error'); return; } // Load anbieter for the modal const anbieter = await apiRequest('/api/anbieter'); const editAnbieterSelect = document.getElementById('editAnbieter'); editAnbieterSelect.innerHTML = ''; anbieter.forEach(a => { editAnbieterSelect.innerHTML += ``; }); // Fill modal with schaltung data document.getElementById('editSchaltungId').value = schaltung.id; document.getElementById('editSchaltungsId').value = schaltung.schaltungs_id; document.getElementById('editEingabeDatum').value = schaltung.eingabe_datum; document.getElementById('editSchaltungsDatum').value = schaltung.schaltungs_datum; document.getElementById('editVorname').value = schaltung.vorname; document.getElementById('editNachname').value = schaltung.nachname; document.getElementById('editFirma').value = schaltung.firma || ''; document.getElementById('editRufnummer').value = schaltung.rufnummer; document.getElementById('editHardware').value = schaltung.hardware || ''; document.getElementById('editHardwareEk').value = schaltung.hardware_ek || ''; document.getElementById('editKundenverkaufspreis').value = schaltung.kundenverkaufspreis_netto || ''; // Toggle hardware pricing visibility toggleEditHardwarePricing(); // Set anbieter and load tarife editAnbieterSelect.value = schaltung.anbieter_id; await loadTarifeForEdit(); document.getElementById('editTarif').value = schaltung.tarif_id; // Load tarifoptionen for the selected tarif await loadTarifoptionenForEdit(); // Load and check currently selected tarifoptionen try { const currentTarifoptionen = await apiRequest(`/api/schaltungen/${id}/tarifoptionen`); console.log('Current tarifoptionen for editing:', currentTarifoptionen); console.log('First option detailed:', JSON.stringify(currentTarifoptionen[0], null, 2)); // Store current tarifoptionen for later use window.currentEditTarifoptionen = currentTarifoptionen; // Wait a bit to ensure tarifoptionen are loaded setTimeout(() => { // Debug: Show all available checkboxes const allCheckboxes = document.querySelectorAll('input[name="editTarifoptionen"]'); console.log('All available checkboxes:', Array.from(allCheckboxes).map(cb => cb.value)); // Check the checkboxes for currently selected options currentTarifoptionen.forEach(option => { // Try both possible field names const optionId = option.tarifoption_id || option.id; console.log(`Looking for checkbox with value: ${optionId} (from tarifoption_id: ${option.tarifoption_id}, id: ${option.id})`); const checkbox = document.querySelector(`input[name="editTarifoptionen"][value="${optionId}"]`); if (checkbox) { console.log(`Found checkbox, checking it`); checkbox.checked = true; // Add visual indicator for currently selected options const label = checkbox.closest('label'); if (label) { label.style.backgroundColor = '#e8f5e8'; label.style.padding = '5px'; label.style.borderRadius = '3px'; label.style.fontWeight = 'bold'; } } else { console.log(`Checkbox not found for tarifoption_id: ${optionId}`); } }); }, 100); } catch (error) { console.error('Fehler beim Laden der aktuellen Tarifoptionen:', error); } // Show modal document.getElementById('editSchaltungModal').style.display = 'block'; // Setup form submission document.getElementById('editSchaltungForm').onsubmit = async (e) => { e.preventDefault(); const hardware = document.getElementById('editHardware').value.trim(); const hardwareEk = document.getElementById('editHardwareEk').value; const kundenverkaufspreis = document.getElementById('editKundenverkaufspreis').value; // Validate hardware pricing if (hardware && hardware !== '') { if (!hardwareEk || !kundenverkaufspreis) { showMessage('Wenn Hardware eingegeben wird, müssen Hardware-EK und Kundenverkaufspreis ausgefüllt werden', 'error'); return; } } // Get selected tarifoptionen const selectedTarifoptionen = []; const checkboxes = document.querySelectorAll('input[name="editTarifoptionen"]:checked'); checkboxes.forEach(checkbox => { selectedTarifoptionen.push(parseInt(checkbox.value)); }); const formData = { schaltungs_id: document.getElementById('editSchaltungsId').value.trim(), eingabe_datum: document.getElementById('editEingabeDatum').value, schaltungs_datum: document.getElementById('editSchaltungsDatum').value, vorname: document.getElementById('editVorname').value.trim(), nachname: document.getElementById('editNachname').value.trim(), firma: document.getElementById('editFirma').value.trim() || null, rufnummer: document.getElementById('editRufnummer').value.trim(), anbieter_id: parseInt(document.getElementById('editAnbieter').value), tarif_id: parseInt(document.getElementById('editTarif').value), hardware: hardware || null, hardware_ek: hardware && hardwareEk ? parseFloat(hardwareEk) : null, kundenverkaufspreis_netto: hardware && kundenverkaufspreis ? parseFloat(kundenverkaufspreis) : null, tarifoptionen: selectedTarifoptionen }; try { await apiRequest(`/api/schaltungen/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); closeModal('editSchaltungModal'); showMessage('Schaltung erfolgreich aktualisiert'); await refreshDataAfterSchaltungChange(); } catch (error) { console.error('Fehler beim Aktualisieren der Schaltung:', error); } }; } catch (error) { console.error('Fehler beim Laden der Schaltungs-Daten:', error); } } function toggleEditHardwarePricing() { const hardwareInput = document.getElementById('editHardware'); const pricingFields = document.getElementById('editHardwarePricingFields'); const hardwareEkInput = document.getElementById('editHardwareEk'); const kundenverkaufspreisInput = document.getElementById('editKundenverkaufspreis'); if (hardwareInput.value.trim() !== '') { pricingFields.style.display = 'block'; hardwareEkInput.required = true; kundenverkaufspreisInput.required = true; } else { pricingFields.style.display = 'none'; hardwareEkInput.required = false; kundenverkaufspreisInput.required = false; hardwareEkInput.value = ''; kundenverkaufspreisInput.value = ''; } } async function loadTarifeForEdit() { const anbieterId = document.getElementById('editAnbieter').value; const tarifSelect = document.getElementById('editTarif'); if (!anbieterId) { tarifSelect.innerHTML = ''; document.getElementById('editTarifoptionenContainer').innerHTML = '
Erst Anbieter wählen...
'; return; } try { const tarife = await apiRequest(`/api/tarife/${anbieterId}`); tarifSelect.innerHTML = ''; tarife.forEach(t => { tarifSelect.innerHTML += ``; }); // Clear tarifoptionen when anbieter changes document.getElementById('editTarifoptionenContainer').innerHTML = '
Erst Tarif wählen...
'; // Add event listener for tarif change tarifSelect.onchange = loadTarifoptionenForEdit; } catch (error) { console.error('Fehler beim Laden der Tarife für Bearbeitung:', error); tarifSelect.innerHTML = ''; } } async function loadTarifoptionenForEdit() { const tarifId = document.getElementById('editTarif').value; const container = document.getElementById('editTarifoptionenContainer'); if (!tarifId) { container.innerHTML = '
Erst Tarif wählen...
'; return; } try { const tarifoptionen = await apiRequest(`/api/tarife/${tarifId}/tarifoptionen`); if (tarifoptionen.length === 0) { container.innerHTML = '
Keine Tarifoptionen verfügbar
'; return; } container.innerHTML = ''; tarifoptionen.forEach(option => { const optionDiv = document.createElement('div'); optionDiv.style.marginBottom = '5px'; const label = document.createElement('label'); label.style.fontWeight = 'normal'; label.style.display = 'block'; label.style.cursor = 'pointer'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.name = 'editTarifoptionen'; checkbox.value = option.id; // Add change event to handle visual feedback checkbox.addEventListener('change', function() { if (this.checked) { label.style.backgroundColor = '#e8f5e8'; label.style.padding = '5px'; label.style.borderRadius = '3px'; label.style.fontWeight = 'bold'; } else { // Check if this was originally selected const wasOriginallySelected = window.currentEditTarifoptionen && window.currentEditTarifoptionen.some(current => (current.tarifoption_id || current.id) == option.id); if (!wasOriginallySelected) { label.style.backgroundColor = ''; label.style.padding = ''; label.style.borderRadius = ''; label.style.fontWeight = 'normal'; } else { // Keep highlighting but show it's deselected label.style.backgroundColor = '#ffe8e8'; label.style.padding = '5px'; label.style.borderRadius = '3px'; label.style.fontWeight = 'bold'; } } }); label.appendChild(checkbox); label.appendChild(document.createTextNode(' ' + option.name + ' ')); const priceSpan = document.createElement('span'); priceSpan.style.color = '#666'; priceSpan.textContent = `(${option.provision_euro >= 0 ? '+' : ''}${option.provision_euro}€)`; label.appendChild(priceSpan); optionDiv.appendChild(label); container.appendChild(optionDiv); }); } catch (error) { console.error('Fehler beim Laden der Tarifoptionen für Bearbeitung:', error); container.innerHTML = '
Fehler beim Laden der Tarifoptionen
'; } } function deleteSchaltung(id, schaltungsId, name) { document.getElementById('deleteSchaltungId').value = id; document.getElementById('deleteSchaltungInfo').textContent = `${schaltungsId} - ${name}`; document.getElementById('deleteReason').value = ''; document.getElementById('deleteSchaltungModal').style.display = 'block'; } async function confirmDeleteSchaltung() { const id = document.getElementById('deleteSchaltungId').value; const reason = document.getElementById('deleteReason').value.trim(); if (!reason) { showMessage('Bitte geben Sie einen Löschgrund ein', 'error'); return; } try { await apiRequest(`/api/schaltungen/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ loeschgrund: reason }) }); closeModal('deleteSchaltungModal'); showMessage('Schaltung erfolgreich gelöscht und archiviert'); await refreshDataAfterSchaltungChange(); } catch (error) { console.error('Fehler beim Löschen der Schaltung:', error); } } // Load Deleted Schaltungen async function loadGeloeschteSchaltungen() { try { const geloeschte = await apiRequest('/api/geloeschte-schaltungen'); const tbody = document.querySelector('#geloeschteSchaltungenTable tbody'); tbody.innerHTML = ''; geloeschte.forEach(s => { const row = document.createElement('tr'); row.innerHTML = ` ${escapeHtml(s.schaltungs_id)} ${escapeHtml(formatDate(s.eingabe_datum))} ${escapeHtml(formatDate(s.schaltungs_datum))} ${escapeHtml(s.vorname)} ${escapeHtml(s.nachname)} ${escapeHtml(s.firma || '-')} ${escapeHtml(s.rufnummer)} ${escapeHtml(s.anbieter_name)} ${escapeHtml(s.tarif_name)} ${escapeHtml(s.hardware || '')} ${escapeHtml(s.loeschgrund)} ${escapeHtml(formatDateTime(s.geloescht_am))} `; tbody.appendChild(row); }); } catch (error) { console.error('Fehler beim Laden der gelöschten Schaltungen:', error); } } function formatDateTime(dateTimeString) { if (!dateTimeString) return ''; const d = new Date(dateTimeString); if (isNaN(d.getTime())) return String(dateTimeString); const dd = String(d.getDate()).padStart(2, '0'); const mm = String(d.getMonth() + 1).padStart(2, '0'); const yyyy = d.getFullYear(); const HH = String(d.getHours()).padStart(2, '0'); const MM = String(d.getMinutes()).padStart(2, '0'); return `${dd}.${mm}.${yyyy} ${HH}:${MM}`; } // Filter functionality let allSchaltungen = []; let allProvisionen = []; // Modified loadSchaltungen to store data and setup filters const originalLoadSchaltungen = loadSchaltungen; loadSchaltungen = async function() { try { const data = await apiRequest('/api/schaltungen'); allSchaltungen = data; // Update filter dropdowns await updateFilterDropdowns(); // Apply current filters applyFiltersOverview(); } catch (error) { console.error('Fehler beim Laden der Schaltungen:', error); } }; // Update filter dropdowns from administration data async function updateFilterDropdowns() { try { // Get all anbieter from administration const anbieter = await apiRequest('/api/anbieter'); // Update Overview anbieter filter const anbieterOverview = document.getElementById('filterAnbieterOverview'); if (anbieterOverview) { anbieterOverview.innerHTML = ''; anbieter.forEach(a => { anbieterOverview.innerHTML += ``; }); } // Update Provisionen anbieter filter const anbieterProvisionen = document.getElementById('filterAnbieterProvisionen'); if (anbieterProvisionen) { anbieterProvisionen.innerHTML = ''; anbieter.forEach(a => { anbieterProvisionen.innerHTML += ``; }); } // Get all tarife from all anbieter const tarifOverview = document.getElementById('filterTarifOverview'); const tarifProvisionen = document.getElementById('filterTarifProvisionen'); if (tarifOverview) { tarifOverview.innerHTML = ''; } if (tarifProvisionen) { tarifProvisionen.innerHTML = ''; } // Load tarife for each anbieter for (const a of anbieter) { try { const tarife = await apiRequest(`/api/tarife/${a.id}`); tarife.forEach(t => { const optionText = `${a.name} - ${t.name}`; const optionValue = t.name; if (tarifOverview) { tarifOverview.innerHTML += ``; } if (tarifProvisionen) { tarifProvisionen.innerHTML += ``; } }); } catch (error) { console.warn(`Fehler beim Laden der Tarife für Anbieter ${a.name}:`, error); } } } catch (error) { console.error('Fehler beim Laden der Filter-Daten:', error); } } // Apply filters for Overview tab function applyFiltersOverview() { const anbieterFilter = document.getElementById('filterAnbieterOverview')?.value || ''; const tarifFilter = document.getElementById('filterTarifOverview')?.value || ''; const schaltungsIdFilter = document.getElementById('filterSchaltungsIdOverview')?.value || ''; const rufnummerFilter = document.getElementById('filterRufnummerOverview')?.value || ''; const datumVon = document.getElementById('filterDatumVonOverview')?.value || ''; const datumBis = document.getElementById('filterDatumBisOverview')?.value || ''; let filteredData = allSchaltungen.filter(schaltung => { // Anbieter filter if (anbieterFilter && schaltung.anbieter_name !== anbieterFilter) { return false; } // Tarif filter if (tarifFilter && schaltung.tarif_name !== tarifFilter) { return false; } // Schaltungs-ID filter (supports multiple comma-separated IDs) if (schaltungsIdFilter) { const schaltungsIds = schaltungsIdFilter.split(',').map(id => id.trim().toLowerCase()).filter(id => id.length > 0); if (schaltungsIds.length > 0) { const schaltungsIdLower = schaltung.schaltungs_id.toLowerCase(); const matches = schaltungsIds.some(searchId => schaltungsIdLower.includes(searchId)); if (!matches) { return false; } } } // Rufnummer filter (case insensitive partial match) if (rufnummerFilter && !schaltung.rufnummer.toLowerCase().includes(rufnummerFilter.toLowerCase())) { return false; } // Date range filter const schaltungsDatum = new Date(schaltung.schaltungs_datum); if (datumVon && schaltungsDatum < new Date(datumVon)) { return false; } if (datumBis && schaltungsDatum > new Date(datumBis)) { return false; } return true; }); // Update table with filtered data updateSchaltungenTable(filteredData); } // Apply filters for Provisionen tab function applyFiltersProvisionen() { const anbieterFilter = document.getElementById('filterAnbieterProvisionen')?.value || ''; const tarifFilter = document.getElementById('filterTarifProvisionen')?.value || ''; const schaltungsIdFilter = document.getElementById('filterSchaltungsIdProvisionen')?.value || ''; const rufnummerFilter = document.getElementById('filterRufnummerProvisionen')?.value || ''; const datumVon = document.getElementById('filterDatumVonProvisionen')?.value || ''; const datumBis = document.getElementById('filterDatumBisProvisionen')?.value || ''; let filteredSchaltungen = allProvisionen.schaltungen.filter(schaltung => { // Anbieter filter if (anbieterFilter && schaltung.anbieter_name !== anbieterFilter) { return false; } // Tarif filter if (tarifFilter && schaltung.tarif_name !== tarifFilter) { return false; } // Schaltungs-ID filter (supports multiple comma-separated IDs) if (schaltungsIdFilter) { const schaltungsIds = schaltungsIdFilter.split(',').map(id => id.trim().toLowerCase()).filter(id => id.length > 0); if (schaltungsIds.length > 0) { const schaltungsIdLower = schaltung.schaltungs_id.toLowerCase(); const matches = schaltungsIds.some(searchId => schaltungsIdLower.includes(searchId)); if (!matches) { return false; } } } // Rufnummer filter (case insensitive partial match) if (rufnummerFilter && !schaltung.rufnummer.toLowerCase().includes(rufnummerFilter.toLowerCase())) { return false; } // Date range filter const schaltungsDatum = new Date(schaltung.schaltungs_datum); if (datumVon && schaltungsDatum < new Date(datumVon)) { return false; } if (datumBis && schaltungsDatum > new Date(datumBis)) { return false; } return true; }); // Recalculate totals for filtered data const totalShopProvision = filteredSchaltungen.reduce((sum, row) => { return sum + parseFloat(row.berechnete_shop_provision || 0); }, 0); const totalMitarbeiterProvision = filteredSchaltungen.reduce((sum, row) => { return sum + parseFloat(row.berechnete_mitarbeiter_provision || 0); }, 0); // Update table and totals with filtered data updateProvisionenTable(filteredSchaltungen, totalShopProvision, totalMitarbeiterProvision); } // Update Schaltungen table with filtered data function updateSchaltungenTable(schaltungen) { const tbody = document.querySelector('#schaltungenTable tbody'); if (!tbody) return; if (!schaltungen || schaltungen.length === 0) { tbody.innerHTML = 'Keine Schaltungen entsprechen den Filterkriterien'; return; } // Delegate to the canonical renderer used by loadSchaltungen. renderSchaltungenRows(schaltungen); } // Update Provisionen table with filtered data function updateProvisionenTable(schaltungen, totalShopProvision, totalMitarbeiterProvision) { const tbody = document.querySelector('#provisionenTable tbody'); const totalShopSpan = document.getElementById('totalShopProvision'); const totalMitarbeiterSpan = document.getElementById('totalMitarbeiterProvision'); if (!tbody) return; tbody.innerHTML = ''; if (schaltungen.length === 0) { // Show "no results" message const row = document.createElement('tr'); row.innerHTML = ` 💰 Keine Provisionen entsprechen den gewählten Filterkriterien `; tbody.appendChild(row); // Reset totals to 0 if (totalShopSpan) totalShopSpan.textContent = '0,00 €'; if (totalMitarbeiterSpan) totalMitarbeiterSpan.textContent = '0,00 €'; return; } schaltungen.forEach(s => { const row = document.createElement('tr'); row.innerHTML = ` ${escapeHtml(s.schaltungs_id)} ${escapeHtml(formatDate(s.schaltungs_datum))} ${escapeHtml(s.vorname)} ${escapeHtml(s.nachname)} ${escapeHtml(s.anbieter_name)} ${escapeHtml(s.tarif_name)} ${escapeHtml(s.berechnete_shop_provision || '0.00')} € ${escapeHtml(s.berechnete_mitarbeiter_provision || '0.00')} € `; tbody.appendChild(row); }); // Update totals if (totalShopSpan) totalShopSpan.textContent = `${totalShopProvision.toFixed(2)} €`; if (totalMitarbeiterSpan) totalMitarbeiterSpan.textContent = `${totalMitarbeiterProvision.toFixed(2)} €`; } // Clear filters functions function clearFiltersOverview() { document.getElementById('filterAnbieterOverview').value = ''; document.getElementById('filterTarifOverview').value = ''; document.getElementById('filterSchaltungsIdOverview').value = ''; document.getElementById('filterRufnummerOverview').value = ''; document.getElementById('filterDatumVonOverview').value = ''; document.getElementById('filterDatumBisOverview').value = ''; applyFiltersOverview(); } function clearFiltersProvisionen() { document.getElementById('filterAnbieterProvisionen').value = ''; document.getElementById('filterTarifProvisionen').value = ''; document.getElementById('filterSchaltungsIdProvisionen').value = ''; document.getElementById('filterRufnummerProvisionen').value = ''; document.getElementById('filterDatumVonProvisionen').value = ''; document.getElementById('filterDatumBisProvisionen').value = ''; applyFiltersProvisionen(); } // Add event listeners for filter changes document.addEventListener('DOMContentLoaded', function() { // Overview filters const overviewFilters = [ 'filterAnbieterOverview', 'filterTarifOverview', 'filterSchaltungsIdOverview', 'filterRufnummerOverview', 'filterDatumVonOverview', 'filterDatumBisOverview' ]; overviewFilters.forEach(filterId => { const element = document.getElementById(filterId); if (element) { if (element.type === 'text') { element.addEventListener('input', applyFiltersOverview); } else { element.addEventListener('change', applyFiltersOverview); } } }); // Provisionen filters const provisionenFilters = [ 'filterAnbieterProvisionen', 'filterTarifProvisionen', 'filterSchaltungsIdProvisionen', 'filterRufnummerProvisionen', 'filterDatumVonProvisionen', 'filterDatumBisProvisionen' ]; provisionenFilters.forEach(filterId => { const element = document.getElementById(filterId); if (element) { if (element.type === 'text') { element.addEventListener('input', applyFiltersProvisionen); } else { element.addEventListener('change', applyFiltersProvisionen); } } }); }); // Tarifoptionen Administration Functions async function loadTarifoptionenAdmin() { await loadAnbieterForTarifoptionen(); await displayTarifoptionenAdmin(); } async function loadAnbieterForTarifoptionen() { try { const anbieter = await apiRequest('/api/anbieter'); const select = document.getElementById('tarifoptionAnbieter'); select.innerHTML = ''; anbieter.forEach(a => { select.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter:', error); } } async function loadTarifeForOptions() { const anbieterId = document.getElementById('tarifoptionAnbieter').value; const container = document.getElementById('tarifCheckboxContainer'); if (!anbieterId) { container.innerHTML = '
Erst Anbieter wählen...
'; return; } try { const tarife = await apiRequest(`/api/tarife/${anbieterId}`); if (tarife.length === 0) { container.innerHTML = '
Keine Tarife für diesen Anbieter gefunden.
'; return; } let checkboxHtml = ''; tarife.forEach(tarif => { checkboxHtml += `
`; }); container.innerHTML = checkboxHtml; } catch (error) { console.error('Fehler beim Laden der Tarife:', error); container.innerHTML = '
Fehler beim Laden der Tarife
'; } } // Toggle tariff selection based on anbieter-wide checkbox function toggleTarifSelection() { const checkbox = document.getElementById('tarifoptionAnbieterWeit'); const tarifSelectionContainer = document.getElementById('tarifSelectionContainer'); const anbieterSelect = document.getElementById('tarifoptionAnbieter'); if (checkbox.checked) { // Anbieter-wide option - hide tarif selection tarifSelectionContainer.style.display = 'none'; } else { // Tariff-specific option - show tarif selection tarifSelectionContainer.style.display = 'block'; if (anbieterSelect.value) { loadTarifeForOptions(); } else { document.getElementById('tarifCheckboxContainer').innerHTML = '
Erst Anbieter wählen...
'; } } } // Select/Deselect all tarifs functions function selectAllTarifs() { const checkboxes = document.querySelectorAll('.tarif-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = true; }); } function deselectAllTarifs() { const checkboxes = document.querySelectorAll('.tarif-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = false; }); } // Edit Tarifoption functions function toggleEditTarifSelection() { const checkbox = document.getElementById('editTarifoptionAnbieterWeit'); const tarifSelectionContainer = document.getElementById('editTarifSelectionContainer'); const anbieterSelect = document.getElementById('editTarifoptionAnbieter'); if (checkbox.checked) { // Anbieter-wide option - hide tarif selection tarifSelectionContainer.style.display = 'none'; } else { // Tariff-specific option - show tarif selection tarifSelectionContainer.style.display = 'block'; if (anbieterSelect.value) { loadTarifeForEditOptions(); } else { document.getElementById('editTarifCheckboxContainer').innerHTML = '
Erst Anbieter wählen...
'; } } } async function loadTarifeForEditOptions() { const anbieterId = document.getElementById('editTarifoptionAnbieter').value; const container = document.getElementById('editTarifCheckboxContainer'); const counterElement = document.getElementById('editTarifCounter'); if (!anbieterId) { container.innerHTML = '
Erst Anbieter wählen...
'; counterElement.textContent = ''; return; } try { const tarife = await apiRequest(`/api/tarife/${anbieterId}`); if (tarife.length === 0) { container.innerHTML = '
Keine Tarife für diesen Anbieter gefunden.
'; counterElement.textContent = ''; return; } let checkboxHtml = '
'; tarife.forEach((tarif, index) => { checkboxHtml += `
`; }); checkboxHtml += '
'; container.innerHTML = checkboxHtml; updateEditTarifCounter(); // Add hover effects container.querySelectorAll('.edit-tarif-item').forEach(item => { item.addEventListener('mouseenter', function() { this.style.backgroundColor = '#e3f2fd'; this.style.borderColor = '#2196f3'; }); item.addEventListener('mouseleave', function() { this.style.backgroundColor = '#f8f9fa'; this.style.borderColor = '#e9ecef'; }); }); // Add change listeners to checkboxes container.querySelectorAll('.edit-tarif-checkbox').forEach(checkbox => { checkbox.addEventListener('change', updateEditTarifCounter); }); } catch (error) { console.error('Fehler beim Laden der Tarife:', error); container.innerHTML = '
Fehler beim Laden der Tarife.
'; counterElement.textContent = ''; } } function selectAllEditTarifs() { const checkboxes = document.querySelectorAll('.edit-tarif-checkbox:not([style*="display: none"])'); checkboxes.forEach(checkbox => { checkbox.checked = true; }); updateEditTarifCounter(); } function deselectAllEditTarifs() { const checkboxes = document.querySelectorAll('.edit-tarif-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = false; }); updateEditTarifCounter(); } function updateEditTarifCounter() { const totalCheckboxes = document.querySelectorAll('.edit-tarif-checkbox').length; const checkedCheckboxes = document.querySelectorAll('.edit-tarif-checkbox:checked').length; const visibleCheckboxes = document.querySelectorAll('.edit-tarif-checkbox:not([style*="display: none"])').length; const counterElement = document.getElementById('editTarifCounter'); if (counterElement) { if (totalCheckboxes === 0) { counterElement.textContent = ''; } else { const filterActive = visibleCheckboxes < totalCheckboxes; counterElement.textContent = `${checkedCheckboxes} von ${totalCheckboxes} Tarifen ausgewählt${filterActive ? ` (${visibleCheckboxes} sichtbar)` : ''}`; } } } function filterEditTarifs() { const searchTerm = document.getElementById('editTarifSearch').value.toLowerCase(); const tarifItems = document.querySelectorAll('.edit-tarif-item'); tarifItems.forEach(item => { const tarifName = item.getAttribute('data-tarif-name'); if (tarifName && tarifName.includes(searchTerm)) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); updateEditTarifCounter(); } // Add Tarifoption Modal Functions function openAddTarifoptionModal() { // Load anbieter for the dropdown loadAnbieterForAddModal(); // Reset form document.getElementById('addTarifoptionForm').reset(); document.getElementById('addTarifoptionAnbieterWeit').checked = true; toggleAddTarifSelection(); // Show modal document.getElementById('addTarifoptionModal').style.display = 'block'; } async function loadAnbieterForAddModal() { try { const anbieter = await apiRequest('/api/anbieter'); const anbieterSelect = document.getElementById('addTarifoptionAnbieter'); anbieterSelect.innerHTML = ''; anbieter.forEach(a => { anbieterSelect.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter:', error); } } function toggleAddTarifSelection() { const checkbox = document.getElementById('addTarifoptionAnbieterWeit'); const tarifSelectionContainer = document.getElementById('addTarifSelectionContainer'); const anbieterWeitInfo = document.getElementById('addAnbieterWeitInfo'); const anbieterSelect = document.getElementById('addTarifoptionAnbieter'); if (checkbox.checked) { // Anbieter-wide option - hide tarif selection tarifSelectionContainer.style.display = 'none'; anbieterWeitInfo.style.display = 'block'; } else { // Tariff-specific option - show tarif selection tarifSelectionContainer.style.display = 'block'; anbieterWeitInfo.style.display = 'none'; if (anbieterSelect.value) { loadTarifeForAddOptions(); } else { document.getElementById('addTarifCheckboxContainer').innerHTML = '
Erst Anbieter wählen...
'; } } } async function loadTarifeForAddOptions() { const anbieterId = document.getElementById('addTarifoptionAnbieter').value; const container = document.getElementById('addTarifCheckboxContainer'); const counterElement = document.getElementById('addTarifCounter'); if (!anbieterId) { container.innerHTML = '
Erst Anbieter wählen...
'; counterElement.textContent = ''; return; } try { const tarife = await apiRequest(`/api/tarife/${anbieterId}`); if (tarife.length === 0) { container.innerHTML = '
Keine Tarife für diesen Anbieter gefunden.
'; counterElement.textContent = ''; return; } let checkboxHtml = '
'; tarife.forEach((tarif, index) => { checkboxHtml += `
`; }); checkboxHtml += '
'; container.innerHTML = checkboxHtml; updateAddTarifCounter(); // Add hover effects container.querySelectorAll('.add-tarif-item').forEach(item => { item.addEventListener('mouseenter', function() { this.style.backgroundColor = '#e3f2fd'; this.style.borderColor = '#2196f3'; }); item.addEventListener('mouseleave', function() { this.style.backgroundColor = '#f8f9fa'; this.style.borderColor = '#e9ecef'; }); }); // Add change listeners to checkboxes container.querySelectorAll('.add-tarif-checkbox').forEach(checkbox => { checkbox.addEventListener('change', updateAddTarifCounter); }); } catch (error) { console.error('Fehler beim Laden der Tarife:', error); container.innerHTML = '
Fehler beim Laden der Tarife.
'; counterElement.textContent = ''; } } function selectAllAddTarifs() { const checkboxes = document.querySelectorAll('.add-tarif-checkbox:not([style*="display: none"])'); checkboxes.forEach(checkbox => { checkbox.checked = true; }); updateAddTarifCounter(); } function deselectAllAddTarifs() { const checkboxes = document.querySelectorAll('.add-tarif-checkbox'); checkboxes.forEach(checkbox => { checkbox.checked = false; }); updateAddTarifCounter(); } function updateAddTarifCounter() { const totalCheckboxes = document.querySelectorAll('.add-tarif-checkbox').length; const checkedCheckboxes = document.querySelectorAll('.add-tarif-checkbox:checked').length; const visibleCheckboxes = document.querySelectorAll('.add-tarif-checkbox:not([style*="display: none"])').length; const counterElement = document.getElementById('addTarifCounter'); if (counterElement) { if (totalCheckboxes === 0) { counterElement.textContent = ''; } else { const filterActive = visibleCheckboxes < totalCheckboxes; counterElement.textContent = `${checkedCheckboxes} von ${totalCheckboxes} Tarifen ausgewählt${filterActive ? ` (${visibleCheckboxes} sichtbar)` : ''}`; } } } function filterAddTarifs() { const searchTerm = document.getElementById('addTarifSearch').value.toLowerCase(); const tarifItems = document.querySelectorAll('.add-tarif-item'); tarifItems.forEach(item => { const tarifName = item.getAttribute('data-tarif-name'); if (tarifName && tarifName.includes(searchTerm)) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); updateAddTarifCounter(); } // Setup form submission for the new add modal document.addEventListener('DOMContentLoaded', function() { const addForm = document.getElementById('addTarifoptionForm'); if (addForm) { addForm.addEventListener('submit', async function(e) { e.preventDefault(); const anbieterId = document.getElementById('addTarifoptionAnbieter').value; const isAnbieterWeit = document.getElementById('addTarifoptionAnbieterWeit').checked; const name = document.getElementById('addTarifoptionName').value; const code = document.getElementById('addTarifoptionCode').value; const provisionEuro = document.getElementById('addTarifoptionProvisionBetrag').value; const beschreibung = document.getElementById('addTarifoptionBeschreibung').value; if (!anbieterId || !name) { showMessage('Bitte Anbieter und Name auswählen', 'error'); return; } let tarifIds = []; if (!isAnbieterWeit) { // Get selected tarifs from checkboxes const checkedBoxes = document.querySelectorAll('.add-tarif-checkbox:checked'); tarifIds = Array.from(checkedBoxes).map(cb => parseInt(cb.value)); if (tarifIds.length === 0) { showMessage('Bitte mindestens einen Tarif auswählen oder Anbieter-weit aktivieren', 'error'); return; } } try { const data = { anbieter_id: anbieterId, tarif_ids: tarifIds, is_anbieter_weit: isAnbieterWeit, name: name, code: code || null, provision_euro: parseFloat(provisionEuro) || 0, beschreibung: beschreibung || null }; const result = await apiRequest('/api/tarifoptionen/multi', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); showMessage(`Tarifoption "${name}" erfolgreich erstellt${result.assignments ? ` (${result.assignments} Tarif-Zuordnungen)` : ''}`, 'success'); // Close modal and refresh admin view closeModal('addTarifoptionModal'); if (typeof displayTarifoptionenAdmin === 'function') { await displayTarifoptionenAdmin(); } } catch (error) { console.error('Fehler beim Erstellen der Tarifoption:', error); // Error message is already shown by apiRequest function } }); } }); async function addTarifoption() { const anbieterId = document.getElementById('tarifoptionAnbieter').value; const isAnbieterWeit = document.getElementById('tarifoptionAnbieterWeit').checked; const name = document.getElementById('tarifoptionName').value; const code = document.getElementById('tarifoptionCode').value; const provisionEuro = document.getElementById('tarifoptionProvisionBetrag').value; const beschreibung = document.getElementById('tarifoptionBeschreibung').value; if (!anbieterId || !name) { showMessage('Bitte Anbieter und Name auswählen', 'error'); return; } let tarifIds = []; if (!isAnbieterWeit) { // Get selected tarifs from checkboxes const checkedBoxes = document.querySelectorAll('.tarif-checkbox:checked'); tarifIds = Array.from(checkedBoxes).map(cb => parseInt(cb.value)); if (tarifIds.length === 0) { showMessage('Bitte mindestens einen Tarif auswählen oder Anbieter-weit aktivieren', 'error'); return; } } try { const data = { anbieter_id: anbieterId, tarif_ids: tarifIds, is_anbieter_weit: isAnbieterWeit, name: name, code: code || null, provision_euro: parseFloat(provisionEuro) || 0, beschreibung: beschreibung || null }; const result = await apiRequest('/api/tarifoptionen/multi', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); showMessage(`Tarifoption "${name}" erfolgreich erstellt (${result.assignments} Tarif-Zuordnungen)`, 'success'); // Reset form document.getElementById('tarifoptionAnbieter').value = ''; document.getElementById('tarifoptionName').value = ''; document.getElementById('tarifoptionCode').value = ''; document.getElementById('tarifoptionProvisionBetrag').value = ''; document.getElementById('tarifoptionBeschreibung').value = ''; document.getElementById('tarifoptionAnbieterWeit').checked = true; toggleTarifSelection(); // Reload tarifoptionen list displayTarifoptionenAdmin(); } catch (error) { console.error('Fehler beim Hinzufügen der Tarifoption:', error); showMessage('Fehler beim Hinzufügen der Tarifoption: ' + error.message, 'error'); } } async function displayTarifoptionenAdmin() { try { // Try to get new format first, fallback to old format let optionen; try { optionen = await apiRequest('/api/tarifoptionen/multi'); } catch (error) { console.log('New API not available, falling back to old format'); optionen = await apiRequest('/api/tarifoptionen'); } const tbody = document.getElementById('tarifoptionenAdminList'); tbody.innerHTML = ''; if (optionen.length === 0) { tbody.innerHTML = 'Keine Tarifoptionen vorhanden'; return; } optionen.forEach(option => { const provision = `${(option.provision_euro || 0).toFixed(2).replace('.', ',')}€`; // tarifDisplayHtml is the rendered HTML; tarifPlain is the plain-text variant for the title attr. let tarifDisplayHtml; let tarifPlain; if (option.tarif_names) { tarifPlain = option.tarif_names || 'Alle Tarife'; tarifDisplayHtml = option.tarif_names ? escapeHtml(option.tarif_names) : 'Alle Tarife'; } else { if (option.tarif_id) { tarifPlain = option.tarif_name || 'Unbekannter Tarif'; tarifDisplayHtml = escapeHtml(tarifPlain); } else { tarifPlain = 'Alle Tarife'; tarifDisplayHtml = 'Alle Tarife'; } } const anbieterName = option.anbieter_name || 'Mehrere Anbieter'; const tr = document.createElement('tr'); tr.innerHTML = ` ${option.id} ${escapeHtml(anbieterName)} ${tarifDisplayHtml} ${escapeHtml(option.name)} ${escapeHtml(option.code || '-')} ${escapeHtml(provision)} ${escapeHtml(option.beschreibung || '-')} `; tr.querySelectorAll('button[data-to-action]').forEach(btn => { btn.addEventListener('click', () => { const action = btn.dataset.toAction; const id = parseInt(btn.dataset.id, 10); if (action === 'edit') editTarifoption(id); else if (action === 'delete') deleteTarifoption(id); }); }); tbody.appendChild(tr); }); } catch (error) { console.error('Fehler beim Laden der Tarifoptionen:', error); const tbody = document.getElementById('tarifoptionenAdminList'); tbody.innerHTML = 'Fehler beim Laden der Daten'; } // Update search counter after loading data updateTarifoptionSearchCounter(); } // Tarifoptionen search functions function filterTarifoptionenAdmin() { const searchTerm = document.getElementById('tarifoptionSearch').value.toLowerCase(); const tbody = document.getElementById('tarifoptionenAdminList'); const rows = tbody.getElementsByTagName('tr'); let visibleCount = 0; for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (row.cells.length < 2) continue; // Skip header or empty rows // Get text content from relevant columns (Anbieter, Tarif, Option, Code, Beschreibung) const anbieterText = (row.cells[1]?.textContent || '').toLowerCase(); const tarifText = (row.cells[2]?.textContent || '').toLowerCase(); const optionText = (row.cells[3]?.textContent || '').toLowerCase(); const codeText = (row.cells[4]?.textContent || '').toLowerCase(); const beschreibungText = (row.cells[6]?.textContent || '').toLowerCase(); // Check if search term matches any of the relevant columns const matches = anbieterText.includes(searchTerm) || tarifText.includes(searchTerm) || optionText.includes(searchTerm) || codeText.includes(searchTerm) || beschreibungText.includes(searchTerm); if (matches || searchTerm === '') { row.style.display = ''; visibleCount++; } else { row.style.display = 'none'; } } updateTarifoptionSearchCounter(); } function clearTarifoptionSearch() { document.getElementById('tarifoptionSearch').value = ''; filterTarifoptionenAdmin(); } function updateTarifoptionSearchCounter() { const tbody = document.getElementById('tarifoptionenAdminList'); const counterElement = document.getElementById('tarifoptionSearchCounter'); const rows = tbody.getElementsByTagName('tr'); let totalCount = 0; let visibleCount = 0; for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (row.cells.length < 2) continue; // Skip header or empty rows totalCount++; if (row.style.display !== 'none') { visibleCount++; } } if (counterElement) { if (totalCount === 0) { counterElement.textContent = ''; } else { const searchTerm = document.getElementById('tarifoptionSearch').value; if (searchTerm && visibleCount < totalCount) { counterElement.textContent = `${visibleCount} von ${totalCount} Optionen`; } else { counterElement.textContent = `${totalCount} Optionen`; } } } } async function editTarifoption(optionId) { try { // Load anbieter for the dropdown const anbieter = await apiRequest('/api/anbieter'); const anbieterSelect = document.getElementById('editTarifoptionAnbieter'); anbieterSelect.innerHTML = ''; anbieter.forEach(a => { anbieterSelect.innerHTML += ``; }); // Try to get from new API first, fallback to old API let optionen; let option; try { optionen = await apiRequest('/api/tarifoptionen/multi'); option = optionen.find(opt => opt.id == optionId); } catch (error) { console.log('New API not available, trying old API'); optionen = await apiRequest('/api/tarifoptionen'); option = optionen.find(opt => opt.id == optionId); } if (!option) { showMessage('Tarifoption nicht gefunden', 'error'); return; } // Fill modal with current data document.getElementById('editTarifoptionId').value = option.id; document.getElementById('editTarifoptionName').value = option.name || ''; document.getElementById('editTarifoptionCode').value = option.code || ''; document.getElementById('editTarifoptionProvisionBetrag').value = option.provision_euro || option.provision_betrag || ''; document.getElementById('editTarifoptionBeschreibung').value = option.beschreibung || ''; // Parse tarif assignments and set anbieter if (option.tarif_ids && option.tarif_ids.length > 0) { // For multi-tarif options, we need to find the anbieter const tarifIds = option.tarif_ids.split(',').map(id => parseInt(id.trim())); if (tarifIds.length > 0) { // Get the first tarif to determine anbieter try { const tarif = await apiRequest(`/api/tarif/${tarifIds[0]}`); if (tarif && tarif.anbieter_id) { anbieterSelect.value = tarif.anbieter_id; // Check if all tarifs belong to the same anbieter let allTarifs = []; for (let tarifId of tarifIds) { try { const t = await apiRequest(`/api/tarif/${tarifId}`); if (t) allTarifs.push(t); } catch (e) {} } const allSameAnbieter = allTarifs.every(t => t.anbieter_id === tarif.anbieter_id); const anbieterTarifs = await apiRequest(`/api/tarife/${tarif.anbieter_id}`); const isAllTarifsOfAnbieter = anbieterTarifs.length === tarifIds.length && anbieterTarifs.every(at => tarifIds.includes(at.id)); if (allSameAnbieter && isAllTarifsOfAnbieter) { // This is an anbieter-wide option document.getElementById('editTarifoptionAnbieterWeit').checked = true; document.getElementById('editTarifSelectionContainer').style.display = 'none'; } else { // This is tarif-specific document.getElementById('editTarifoptionAnbieterWeit').checked = false; document.getElementById('editTarifSelectionContainer').style.display = 'block'; // Load and select specific tarifs await loadTarifeForEditOptions(); // Select the specific tarifs setTimeout(() => { tarifIds.forEach(tarifId => { const checkbox = document.querySelector(`.edit-tarif-checkbox[value="${tarifId}"]`); if (checkbox) checkbox.checked = true; }); }, 100); } } } catch (error) { console.error('Error loading tarif details:', error); } } } else { // Default to anbieter-wide if no specific tarifs document.getElementById('editTarifoptionAnbieterWeit').checked = true; document.getElementById('editTarifSelectionContainer').style.display = 'none'; } // Show modal document.getElementById('editTarifoptionModal').style.display = 'block'; // Setup form submission document.getElementById('editTarifoptionForm').onsubmit = async (e) => { e.preventDefault(); const anbieterId = document.getElementById('editTarifoptionAnbieter').value; const isAnbieterWeit = document.getElementById('editTarifoptionAnbieterWeit').checked; if (!anbieterId) { showMessage('Bitte wählen Sie einen Anbieter aus', 'error'); return; } let tarifIds = []; if (!isAnbieterWeit) { const checkedBoxes = document.querySelectorAll('.edit-tarif-checkbox:checked'); tarifIds = Array.from(checkedBoxes).map(cb => parseInt(cb.value)); if (tarifIds.length === 0) { showMessage('Bitte wählen Sie mindestens einen Tarif aus oder aktivieren Sie "Anbieter-weit"', 'error'); return; } } const formData = { name: document.getElementById('editTarifoptionName').value.trim(), code: document.getElementById('editTarifoptionCode').value.trim() || null, provision_euro: document.getElementById('editTarifoptionProvisionBetrag').value ? parseFloat(document.getElementById('editTarifoptionProvisionBetrag').value) : 0, beschreibung: document.getElementById('editTarifoptionBeschreibung').value.trim() || null, anbieter_id: parseInt(anbieterId), is_anbieter_weit: isAnbieterWeit, tarif_ids: tarifIds }; try { // Try new API first, fallback to old API try { await apiRequest(`/api/tarifoptionen/multi/${optionId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); } catch (error) { console.log('New API not available, trying old API'); await apiRequest(`/api/tarifoptionen/${optionId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: formData.name, provision_euro: formData.provision_euro }) }); } closeModal('editTarifoptionModal'); showMessage('Tarifoption erfolgreich aktualisiert'); await displayTarifoptionenAdmin(); } catch (error) { console.error('Fehler beim Aktualisieren der Tarifoption:', error); // Error message is already shown by apiRequest function } }; } catch (error) { console.error('Fehler beim Laden der Tarifoption-Daten:', error); } } async function deleteTarifoption(optionId) { if (!confirm('Möchten Sie diese Tarifoption wirklich löschen? Dies entfernt auch alle Zuordnungen zu Tarifen.')) { return; } try { // Try new API first, fallback to old API let success = false; try { await apiRequest(`/api/tarifoptionen/multi/${optionId}`, { method: 'DELETE' }); success = true; } catch (error) { console.log('New API not available, trying old API'); await apiRequest(`/api/tarifoptionen/${optionId}`, { method: 'DELETE' }); success = true; } if (success) { await displayTarifoptionenAdmin(); showMessage('Tarifoption erfolgreich gelöscht', 'success'); } } catch (error) { console.error('Fehler beim Löschen der Tarifoption:', error); showMessage('Fehler beim Löschen der Tarifoption: ' + (error.message || 'Unbekannter Fehler'), 'error'); } } // Mitarbeiter-Prozentsätze Functions async function loadMitarbeiterProzentsaetzeAdmin() { try { const anbieter = await apiRequest('/api/anbieter'); const container = document.getElementById('mitarbeiterProzentsaetzeList'); container.innerHTML = ''; for (const a of anbieter) { const prozentsaetze = await apiRequest(`/api/anbieter/${a.id}/mitarbeiter-prozentsaetze`); const providerDiv = document.createElement('div'); providerDiv.className = 'admin-card'; providerDiv.innerHTML = `

${escapeHtml(a.name)}

`; container.appendChild(providerDiv); const prozentsaetzeList = document.getElementById(`prozentsaetze-${a.id}`); if (prozentsaetze.length === 0) { prozentsaetzeList.innerHTML = '

Keine Mitarbeiter-Prozentsätze definiert

'; } else { prozentsaetze.forEach(p => { const item = document.createElement('div'); item.className = 'list-item'; item.innerHTML = `
${escapeHtml(String(p.prozentsatz))}% ${p.beschreibung ? `- ${escapeHtml(p.beschreibung)}` : ''}
Gültig ab: ${escapeHtml(formatDate(p.gueltig_ab))}
`; item.querySelector('button[data-mp-action]').addEventListener('click', (e) => { deleteMitarbeiterProzentsatz(parseInt(e.currentTarget.dataset.id, 10)); }); prozentsaetzeList.appendChild(item); }); } } } catch (error) { console.error('Fehler beim Laden der Mitarbeiter-Prozentsätze:', error); } } async function loadMitarbeiterAnbieter() { try { const anbieter = await apiRequest('/api/anbieter'); const select = document.getElementById('mitarbeiterAnbieter'); select.innerHTML = ''; anbieter.forEach(a => { select.innerHTML += ``; }); } catch (error) { console.error('Fehler beim Laden der Anbieter:', error); } } async function addMitarbeiterProzentsatz() { const anbieterId = document.getElementById('mitarbeiterAnbieter').value; const prozentsatz = document.getElementById('mitarbeiterProzentsatz').value; const gueltigAb = document.getElementById('mitarbeiterGueltigAb').value; const beschreibung = document.getElementById('mitarbeiterBeschreibung').value.trim(); if (!anbieterId || !prozentsatz) { showMessage('Bitte Anbieter und Prozentsatz auswählen', 'error'); return; } const prozentsatzNum = parseFloat(prozentsatz); if (prozentsatzNum < 0 || prozentsatzNum > 100) { showMessage('Prozentsatz muss zwischen 0 und 100 sein', 'error'); return; } try { const data = { anbieter_id: anbieterId, prozentsatz: prozentsatzNum, gueltig_ab: gueltigAb || new Date().toISOString().split('T')[0], beschreibung: beschreibung || null }; await apiRequest('/api/mitarbeiter-prozentsaetze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); // Reset form document.getElementById('mitarbeiterAnbieter').value = ''; document.getElementById('mitarbeiterProzentsatz').value = ''; document.getElementById('mitarbeiterGueltigAb').value = ''; document.getElementById('mitarbeiterBeschreibung').value = ''; await loadMitarbeiterProzentsaetzeAdmin(); showMessage('Mitarbeiter-Prozentsatz erfolgreich hinzugefügt'); } catch (error) { console.error('Fehler beim Hinzufügen des Mitarbeiter-Prozentsatzes:', error); showMessage('Fehler beim Hinzufügen des Mitarbeiter-Prozentsatzes', 'error'); } } async function deleteMitarbeiterProzentsatz(id) { if (!confirm('Möchten Sie diesen Mitarbeiter-Prozentsatz wirklich löschen?')) { return; } try { await apiRequest(`/api/mitarbeiter-prozentsaetze/${id}`, { method: 'DELETE' }); await loadMitarbeiterProzentsaetzeAdmin(); showMessage('Mitarbeiter-Prozentsatz erfolgreich gelöscht'); } catch (error) { console.error('Fehler beim Löschen des Mitarbeiter-Prozentsatzes:', error); showMessage('Fehler beim Löschen des Mitarbeiter-Prozentsatzes', 'error'); } } // Tarif-Provisionen Functions async function loadTarifProvisionsAdmin() { try { const tarife = await apiRequest('/api/tarife'); const container = document.getElementById('tarifProvisionsList'); container.innerHTML = ''; for (const tarif of tarife) { const tarifDiv = document.createElement('div'); tarifDiv.className = 'admin-card'; tarifDiv.innerHTML = `

${escapeHtml(tarif.name)} (${escapeHtml(tarif.anbieter_name)})

Basis-Provision: ${escapeHtml(String(tarif.provision_euro))}€
Feste Mitarbeiter-Provision für diesen Tarif
`; tarifDiv.querySelector('button[data-tp-action]').addEventListener('click', (e) => { saveTarifProvision(parseInt(e.currentTarget.dataset.id, 10)); }); container.appendChild(tarifDiv); } } catch (error) { console.error('Fehler beim Laden der Tarif-Provisionen:', error); } } async function saveTarifProvision(tarifId) { const provisionInput = document.getElementById(`provision-${tarifId}`); const provision = parseFloat(provisionInput.value) || 0; try { await apiRequest(`/api/tarife/${tarifId}/mitarbeiter-provision`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mitarbeiter_provision: provision }) }); showMessage('Mitarbeiter-Provision erfolgreich gespeichert'); } catch (error) { console.error('Fehler beim Speichern der Mitarbeiter-Provision:', error); showMessage('Fehler beim Speichern der Mitarbeiter-Provision', 'error'); } } // PDF Viewer Functions let currentPdf = null; let currentPage = 1; let totalPages = 0; let currentScale = 1.0; const scaleStep = 0.2; // Configure PDF.js worker pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; async function viewPdfDocument(documentId, filename) { try { // Show loading message showMessage('PDF wird geladen...', 'success'); // Set PDF title document.getElementById('pdfViewerTitle').textContent = `📋 ${filename}`; // Load PDF const pdfUrl = `/api/dokumente/${documentId}/download`; const loadingTask = pdfjsLib.getDocument(pdfUrl); currentPdf = await loadingTask.promise; totalPages = currentPdf.numPages; currentPage = 1; currentScale = 1.0; // Update UI document.getElementById('totalPages').textContent = totalPages; document.getElementById('zoomLevel').textContent = '100%'; // Render first page await renderPage(currentPage); // Show modal document.getElementById('pdfViewerModal').classList.add('show'); // Update navigation buttons updateNavigationButtons(); } catch (error) { console.error('Fehler beim Laden des PDFs:', error); showMessage('Fehler beim Laden des PDFs', 'error'); } } async function renderPage(pageNum) { try { const page = await currentPdf.getPage(pageNum); const canvas = document.getElementById('pdfCanvas'); const context = canvas.getContext('2d'); // Calculate viewport const viewport = page.getViewport({ scale: currentScale }); // Set canvas dimensions canvas.height = viewport.height; canvas.width = viewport.width; // Render PDF page const renderContext = { canvasContext: context, viewport: viewport }; await page.render(renderContext).promise; // Update page number document.getElementById('currentPage').textContent = currentPage; } catch (error) { console.error('Fehler beim Rendern der Seite:', error); showMessage('Fehler beim Rendern der Seite', 'error'); } } function updateNavigationButtons() { const prevBtn = document.getElementById('prevPageBtn'); const nextBtn = document.getElementById('nextPageBtn'); prevBtn.disabled = currentPage <= 1; nextBtn.disabled = currentPage >= totalPages; } async function prevPage() { if (currentPage > 1) { currentPage--; await renderPage(currentPage); updateNavigationButtons(); } } async function nextPage() { if (currentPage < totalPages) { currentPage++; await renderPage(currentPage); updateNavigationButtons(); } } async function zoomIn() { currentScale += scaleStep; await renderPage(currentPage); updateZoomLevel(); } async function zoomOut() { if (currentScale > scaleStep) { currentScale -= scaleStep; await renderPage(currentPage); updateZoomLevel(); } } function updateZoomLevel() { const zoomPercent = Math.round(currentScale * 100); document.getElementById('zoomLevel').textContent = `${zoomPercent}%`; } function closePdfViewer() { document.getElementById('pdfViewerModal').classList.remove('show'); // Cleanup currentPdf = null; currentPage = 1; totalPages = 0; currentScale = 1.0; // Clear canvas const canvas = document.getElementById('pdfCanvas'); const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); } // Close PDF viewer when clicking outside document.addEventListener('click', function(event) { const modal = document.getElementById('pdfViewerModal'); if (event.target === modal) { closePdfViewer(); } }); // Keyboard navigation for PDF viewer document.addEventListener('keydown', function(event) { const modal = document.getElementById('pdfViewerModal'); if (modal.classList.contains('show')) { switch(event.key) { case 'ArrowLeft': case 'ArrowUp': event.preventDefault(); prevPage(); break; case 'ArrowRight': case 'ArrowDown': event.preventDefault(); nextPage(); break; case 'Escape': event.preventDefault(); closePdfViewer(); break; case '+': case '=': event.preventDefault(); zoomIn(); break; case '-': event.preventDefault(); zoomOut(); break; } } }); // Hardware pricing fields toggle and validation function toggleHardwarePricing() { const hardwareInput = document.getElementById('hardware'); const hardwarePricingFields = document.getElementById('hardwarePricingFields'); const hardwareEkInput = document.getElementById('hardwareEk'); const kundenverkaufspreisInput = document.getElementById('kundenverkaufspreis'); if (hardwareInput.value.trim() !== '') { // Show pricing fields and make them required hardwarePricingFields.style.display = 'block'; hardwareEkInput.required = true; kundenverkaufspreisInput.required = true; } else { // Hide pricing fields and remove required attribute hardwarePricingFields.style.display = 'none'; hardwareEkInput.required = false; kundenverkaufspreisInput.required = false; // Clear values hardwareEkInput.value = ''; kundenverkaufspreisInput.value = ''; } } // Add hardware pricing validation to form submission function validateHardwarePricing() { const hardwareInput = document.getElementById('hardware'); const hardwareEkInput = document.getElementById('hardwareEk'); const kundenverkaufspreisInput = document.getElementById('kundenverkaufspreis'); if (hardwareInput.value.trim() !== '') { if (!hardwareEkInput.value || parseFloat(hardwareEkInput.value) < 0) { showMessage('Hardware-EK ist erforderlich wenn Hardware angegeben wird', 'error'); return false; } if (!kundenverkaufspreisInput.value || parseFloat(kundenverkaufspreisInput.value) < 0) { showMessage('Kundenverkaufspreis ist erforderlich wenn Hardware angegeben wird', 'error'); return false; } } return true; } // PDF Export Function for Provisionen function exportProvisionenToPdf() { try { // Get current filter values const anbieterFilter = document.getElementById('filterAnbieterProvisionen')?.value || ''; const tarifFilter = document.getElementById('filterTarifProvisionen')?.value || ''; const schaltungsIdFilter = document.getElementById('filterSchaltungsIdProvisionen')?.value || ''; const rufnummerFilter = document.getElementById('filterRufnummerProvisionen')?.value || ''; const datumVon = document.getElementById('filterDatumVonProvisionen')?.value || ''; const datumBis = document.getElementById('filterDatumBisProvisionen')?.value || ''; // Get filtered data (same logic as applyFiltersProvisionen) let filteredSchaltungen = []; if (allProvisionen && allProvisionen.schaltungen) { filteredSchaltungen = allProvisionen.schaltungen.filter(schaltung => { // Anbieter filter if (anbieterFilter && schaltung.anbieter_name !== anbieterFilter) { return false; } // Tarif filter if (tarifFilter && schaltung.tarif_name !== tarifFilter) { return false; } // Schaltungs-ID filter (supports multiple comma-separated IDs) if (schaltungsIdFilter) { const schaltungsIds = schaltungsIdFilter.split(',').map(id => id.trim().toLowerCase()).filter(id => id.length > 0); if (schaltungsIds.length > 0) { const schaltungsIdLower = schaltung.schaltungs_id.toLowerCase(); const matches = schaltungsIds.some(searchId => schaltungsIdLower.includes(searchId)); if (!matches) { return false; } } } // Rufnummer filter (case insensitive partial match) if (rufnummerFilter && !schaltung.rufnummer.toLowerCase().includes(rufnummerFilter.toLowerCase())) { return false; } // Date range filter const schaltungsDatum = new Date(schaltung.schaltungs_datum); if (datumVon && schaltungsDatum < new Date(datumVon)) { return false; } if (datumBis && schaltungsDatum > new Date(datumBis)) { return false; } return true; }); } if (filteredSchaltungen.length === 0) { showMessage('Keine Daten zum Exportieren gefunden', 'error'); return; } // Calculate totals const totalMitarbeiterProvision = filteredSchaltungen.reduce((sum, row) => { return sum + parseFloat(row.berechnete_mitarbeiter_provision || 0); }, 0); const totalShopProvision = filteredSchaltungen.reduce((sum, row) => { return sum + parseFloat(row.berechnete_shop_provision || 0); }, 0); // Create PDF in landscape orientation const { jsPDF } = window.jspdf; const doc = new jsPDF('landscape'); // Add title doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.text('Provisions-Übersicht', 20, 20); // Add filter info if any filters are applied let yPos = 35; doc.setFontSize(10); doc.setFont(undefined, 'normal'); if (anbieterFilter || tarifFilter || datumVon || datumBis) { doc.text('Aktive Filter:', 20, yPos); yPos += 6; if (anbieterFilter) { doc.text(`• Anbieter: ${anbieterFilter}`, 25, yPos); yPos += 5; } if (tarifFilter) { doc.text(`• Tarif: ${tarifFilter}`, 25, yPos); yPos += 5; } if (datumVon) { doc.text(`• Von: ${formatDate(datumVon)}`, 25, yPos); yPos += 5; } if (datumBis) { doc.text(`• Bis: ${formatDate(datumBis)}`, 25, yPos); yPos += 5; } yPos += 5; } // Add generation date doc.text(`Erstellt am: ${formatDateTime(new Date())}`, 20, yPos); yPos += 15; // Table headers const tableHeaders = [ 'Schaltungs-ID', 'Eingabe-Datum', 'Schaltungsdatum', 'Kunde', 'Anbieter', 'Tarif', 'Hardware', 'Hardware-EK', 'Verkaufspreis', 'Shop-Provision', 'Mitarbeiter-Provision' ]; // Set table properties for landscape orientation (11 columns now) const columnWidths = [20, 20, 20, 35, 25, 30, 20, 20, 20, 25, 25]; // Adjusted for 11 columns const startX = 10; const rowHeight = 12; // Increased row height for text wrapping let currentY = yPos; // Draw headers doc.setFontSize(8); doc.setFont(undefined, 'bold'); let currentX = startX; tableHeaders.forEach((header, index) => { doc.rect(currentX, currentY, columnWidths[index], rowHeight); doc.text(header, currentX + 2, currentY + 8); currentX += columnWidths[index]; }); currentY += rowHeight; // Draw data rows doc.setFont(undefined, 'normal'); // Helper function to wrap text const wrapText = (text, maxWidth) => { if (!text) return ['']; const words = text.toString().split(' '); const lines = []; let currentLine = ''; for (const word of words) { const testLine = currentLine ? `${currentLine} ${word}` : word; const testWidth = doc.getTextWidth(testLine); if (testWidth > maxWidth - 4) { // -4 for padding if (currentLine) { lines.push(currentLine); currentLine = word; } else { lines.push(word); // Single word too long, force it } } else { currentLine = testLine; } } if (currentLine) { lines.push(currentLine); } return lines.length > 0 ? lines : ['']; }; filteredSchaltungen.forEach((schaltung, rowIndex) => { // Check if we need a new page (landscape has more space) if (currentY > 180) { doc.addPage(); currentY = 20; // Redraw headers on new page doc.setFont(undefined, 'bold'); currentX = startX; tableHeaders.forEach((header, index) => { doc.rect(currentX, currentY, columnWidths[index], rowHeight); doc.text(header, currentX + 2, currentY + 8); currentX += columnWidths[index]; }); currentY += rowHeight; doc.setFont(undefined, 'normal'); } // Prepare row data const rawData = [ schaltung.schaltungs_id || '', schaltung.eingabe_datum ? formatDate(schaltung.eingabe_datum) : '', schaltung.schaltungs_datum ? formatDate(schaltung.schaltungs_datum) : '', `${schaltung.vorname || ''} ${schaltung.nachname || ''}`.trim() + (schaltung.firma ? ` (${schaltung.firma})` : ''), schaltung.anbieter_name || '', schaltung.tarif_name || '', schaltung.hardware || '-', schaltung.hardware_ek ? `${parseFloat(schaltung.hardware_ek).toFixed(2)} €` : '-', schaltung.kundenverkaufspreis_netto ? `${parseFloat(schaltung.kundenverkaufspreis_netto).toFixed(2)} €` : '-', `${schaltung.berechnete_shop_provision || '0.00'} €`, `${schaltung.berechnete_mitarbeiter_provision || '0.00'} €` ]; // Wrap text for each column const wrappedData = rawData.map((data, index) => wrapText(data, columnWidths[index])); // Calculate actual row height needed const maxLines = Math.max(...wrappedData.map(lines => lines.length)); const actualRowHeight = Math.max(rowHeight, maxLines * 4 + 4); currentX = startX; // Draw row with proper text wrapping wrappedData.forEach((lines, colIndex) => { // Draw cell border doc.rect(currentX, currentY, columnWidths[colIndex], actualRowHeight); // Draw text lines lines.forEach((line, lineIndex) => { const textY = currentY + 6 + (lineIndex * 4); doc.text(line, currentX + 2, textY); }); currentX += columnWidths[colIndex]; }); currentY += actualRowHeight; }); // Add totals currentY += 10; doc.setFont(undefined, 'bold'); doc.setFontSize(10); doc.text(`Gesamte Shop-Provision: ${totalShopProvision.toFixed(2)} €`, startX, currentY); currentY += 8; doc.text(`Gesamte Mitarbeiter-Provision: ${totalMitarbeiterProvision.toFixed(2)} €`, startX, currentY); currentY += 8; doc.text(`Gesamt: ${(totalShopProvision + totalMitarbeiterProvision).toFixed(2)} €`, startX, currentY); // Generate filename with current date and filters let filename = 'Provisionen_'; if (datumVon || datumBis) { if (datumVon) filename += `von_${datumVon}_`; if (datumBis) filename += `bis_${datumBis}_`; } filename += new Date().toISOString().split('T')[0] + '.pdf'; // Save the PDF doc.save(filename); showMessage('PDF erfolgreich erstellt und heruntergeladen!', 'success'); } catch (error) { console.error('Fehler beim PDF-Export:', error); showMessage('Fehler beim Erstellen des PDFs', 'error'); } } // Add form validation for hardware pricing document.addEventListener('DOMContentLoaded', function() { const schaltungForm = document.getElementById('schaltungForm'); if (schaltungForm) { schaltungForm.addEventListener('submit', function(event) { if (!validateHardwarePricing()) { event.preventDefault(); return false; } }); } }); // ============================================================================ // Benutzer-Verwaltung (admin only). Backend: /api/users/* // ============================================================================ async function loadUsers() { const tbody = document.getElementById('usersTableBody'); if (!tbody) return; tbody.innerHTML = 'Lade...'; try { const users = await apiRequest('/api/users'); tbody.innerHTML = ''; if (!users.length) { tbody.innerHTML = 'Keine Benutzer'; return; } users.forEach(u => { const tr = document.createElement('tr'); const isSelf = currentUser && currentUser.username === u.username; tr.innerHTML = ` ${escapeHtml(u.username)}${isSelf ? ' (Sie)' : ''} ${escapeHtml(u.displayName || '')} ${u.isAdmin ? '✓' : ''} ${escapeHtml(formatDateTime(u.createdAt))} `; tr.querySelectorAll('button[data-user-action]').forEach(btn => { btn.addEventListener('click', () => { const action = btn.dataset.userAction; const uuid = btn.dataset.uuid; if (action === 'edit') openUserModal(u); else if (action === 'delete') deleteUser(uuid, btn.dataset.username); }); }); tbody.appendChild(tr); }); } catch (err) { tbody.innerHTML = 'Fehler beim Laden'; } } function openUserModal(user) { const isEdit = !!user; document.getElementById('userModalTitle').textContent = isEdit ? 'Benutzer bearbeiten' : 'Neuer Benutzer'; document.getElementById('userModalUuid').value = isEdit ? user.uuid : ''; document.getElementById('userModalUsername').value = isEdit ? user.username : ''; document.getElementById('userModalDisplay').value = isEdit ? (user.displayName || '') : ''; document.getElementById('userModalPassword').value = ''; document.getElementById('userModalIsAdmin').checked = isEdit ? !!user.isAdmin : false; document.getElementById('userModalPasswordHint').style.display = isEdit ? 'none' : ''; document.getElementById('userModalPasswordEditHint').style.display = isEdit ? '' : 'none'; document.getElementById('userModal').style.display = 'block'; } function closeUserModal() { document.getElementById('userModal').style.display = 'none'; } async function saveUserFromModal() { const uuid = document.getElementById('userModalUuid').value; const username = document.getElementById('userModalUsername').value.trim(); const displayName = document.getElementById('userModalDisplay').value.trim(); const password = document.getElementById('userModalPassword').value; const isAdmin = document.getElementById('userModalIsAdmin').checked; if (!username) { showMessage('Username ist erforderlich', 'error'); return; } try { if (uuid) { // Update existing user const body = { username, displayName, isAdmin }; if (password) body.newPassword = password; await apiRequest(`/api/users/${encodeURIComponent(uuid)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); showMessage(`Benutzer "${username}" aktualisiert`); } else { // Create new user if (!password || password.length < 8) { showMessage('Passwort muss mindestens 8 Zeichen lang sein', 'error'); return; } await apiRequest('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, displayName, isAdmin }), }); showMessage(`Benutzer "${username}" angelegt`); } closeUserModal(); loadUsers(); } catch (err) { // apiRequest already shows the error message } } async function deleteUser(uuid, username) { if (!confirm(`Benutzer "${username}" wirklich löschen?`)) return; try { await apiRequest(`/api/users/${encodeURIComponent(uuid)}`, { method: 'DELETE' }); showMessage(`Benutzer "${username}" gelöscht`); loadUsers(); } catch (err) { // apiRequest already shows the error message } } // ============================================================================ // VO-Verwaltung (Vertriebspartner-Nummern). Backend: /api/vos // ============================================================================ async function loadAnbieterForVoForm() { try { const anbieter = await apiRequest('/api/anbieter'); const select = document.getElementById('voAnbieter'); if (!select) return; select.innerHTML = ''; anbieter.forEach(a => { select.innerHTML += ``; }); } catch (e) { /* error already shown */ } } async function loadVosAdmin() { const tbody = document.getElementById('vosList'); if (!tbody) return; tbody.innerHTML = 'Lade...'; try { // Load anbieter to know each one's default_vo_id. const [vos, anbieter] = await Promise.all([ apiRequest('/api/vos'), apiRequest('/api/anbieter'), ]); const defaultByAnbieter = {}; anbieter.forEach(a => { defaultByAnbieter[a.id] = a.default_vo_id; }); tbody.innerHTML = ''; if (!vos.length) { tbody.innerHTML = 'Keine VOs vorhanden'; return; } vos.forEach(v => { const isDefault = defaultByAnbieter[v.anbieter_id] === v.id; const tr = document.createElement('tr'); tr.innerHTML = ` ${escapeHtml(v.anbieter_name)} ${escapeHtml(v.nummer)} ${escapeHtml(v.bezeichnung || '')} ${v.aktiv ? 'Ja' : 'Nein'} ${isDefault ? '★' : ''} ${isDefault ? `` : ``} `; tr.querySelectorAll('button[data-vo-action]').forEach(btn => { btn.addEventListener('click', () => { const action = btn.dataset.voAction; if (action === 'edit') { editVo(parseInt(btn.dataset.id, 10), btn.dataset.nummer, btn.dataset.bezeichnung, btn.dataset.aktiv === '1', btn.dataset.anbieterName); } else if (action === 'delete') { deleteVo(parseInt(btn.dataset.id, 10), btn.dataset.label); } else if (action === 'set-default') { setAnbieterDefaultVo(parseInt(btn.dataset.anbieterId, 10), parseInt(btn.dataset.id, 10)); } else if (action === 'unset-default') { setAnbieterDefaultVo(parseInt(btn.dataset.anbieterId, 10), null); } }); }); tbody.appendChild(tr); }); } catch (e) { tbody.innerHTML = 'Fehler beim Laden'; } } async function setAnbieterDefaultVo(anbieterId, voId) { try { await apiRequest(`/api/anbieter/${anbieterId}/default-vo`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ vo_id: voId }), }); showMessage(voId ? 'Standard-VO gesetzt' : 'Standard-VO entfernt'); loadVosAdmin(); } catch (e) { /* error already shown */ } } async function addVo() { const anbieter_id = document.getElementById('voAnbieter').value; const nummer = document.getElementById('voNummer').value.trim(); const bezeichnung = document.getElementById('voBezeichnung').value.trim(); if (!anbieter_id || !nummer) { showMessage('Anbieter und VO-Nummer sind erforderlich', 'error'); return; } try { await apiRequest('/api/vos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ anbieter_id, nummer, bezeichnung }), }); document.getElementById('voAnbieter').value = ''; document.getElementById('voNummer').value = ''; document.getElementById('voBezeichnung').value = ''; showMessage('VO erfolgreich hinzugefügt'); loadVosAdmin(); } catch (e) { /* already shown */ } } function editVo(id, nummer, bezeichnung, aktiv, anbieterName) { document.getElementById('voEditId').value = id; document.getElementById('voEditAnbieter').value = anbieterName || ''; document.getElementById('voEditNummer').value = nummer || ''; document.getElementById('voEditBezeichnung').value = bezeichnung || ''; document.getElementById('voEditAktiv').checked = !!aktiv; document.getElementById('voEditModal').style.display = 'block'; } function closeVoEditModal() { document.getElementById('voEditModal').style.display = 'none'; } async function saveVoFromModal() { const id = document.getElementById('voEditId').value; const nummer = document.getElementById('voEditNummer').value.trim(); const bezeichnung = document.getElementById('voEditBezeichnung').value.trim(); const aktiv = document.getElementById('voEditAktiv').checked; if (!nummer) { showMessage('VO-Nummer darf nicht leer sein', 'error'); return; } try { await apiRequest(`/api/vos/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nummer, bezeichnung: bezeichnung || null, aktiv }), }); closeVoEditModal(); showMessage('VO aktualisiert'); loadVosAdmin(); } catch (e) { /* already shown */ } } async function deleteVo(id, label) { if (!confirm(`VO "${label}" wirklich löschen?`)) return; try { await apiRequest(`/api/vos/${id}`, { method: 'DELETE' }); showMessage('VO gelöscht'); loadVosAdmin(); } catch (e) { /* already shown */ } }