// 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();
// Set today's date as default
const today = new Date().toISOString().split('T')[0];
document.getElementById('eingabeDatum').value = today;
// Setup form submission
document.getElementById('schaltungForm').addEventListener('submit', handleSchaltungSubmit);
// Load all data in background for faster tab switching
preloadAllData();
});
function renderUserBadge() {
if (!currentUser) return;
// Show admin-only navigation elements (e.g. the Benutzer tab) once we know the role.
document.querySelectorAll('.admin-only').forEach(el => {
el.style.display = currentUser.isAdmin ? '' : 'none';
});
let badge = document.getElementById('userBadge');
if (!badge) {
badge = document.createElement('div');
badge.id = 'userBadge';
badge.style.cssText = 'position:fixed;top:12px;right:16px;z-index:9000;background:rgba(255,255,255,0.95);padding:8px 14px;border-radius:6px;font-size:13px;box-shadow:0 2px 6px rgba(0,0,0,0.15);font-family:Segoe UI,Tahoma,sans-serif;';
document.body.appendChild(badge);
}
const name = currentUser.displayName || currentUser.username;
badge.innerHTML = '';
const span = document.createElement('span');
span.textContent = name + ' · ';
const link = document.createElement('a');
link.href = '#';
link.textContent = 'Abmelden';
link.style.color = '#c0392b';
link.style.textDecoration = 'none';
link.addEventListener('click', async (e) => {
e.preventDefault();
try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); } catch (_) {}
window.location.href = '/login';
});
badge.appendChild(span);
badge.appendChild(link);
}
// 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');
const anbieterSelect = document.getElementById('anbieter');
const tarifAnbieterSelect = document.getElementById('tarifAnbieter');
// Clear existing options
anbieterSelect.innerHTML = '';
tarifAnbieterSelect.innerHTML = '';
anbieter.forEach(a => {
anbieterSelect.innerHTML += ``;
tarifAnbieterSelect.innerHTML += ``;
});
} 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());
// Collect selected tarifoptionen
const selectedTarifoptionen = Array.from(document.querySelectorAll('input[name="tarifoptionen"]:checked'))
.map(checkbox => parseInt(checkbox.value));
// Validate hardware length if provided
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;
// Add selected tarifoptionen to the schaltung
if (selectedTarifoptionen.length > 0) {
await addTarifoptionenToSchaltung(currentSchaltungId, selectedTarifoptionen);
}
// Upload files if any
if (uploadedFiles.length > 0) {
await uploadDocuments(currentSchaltungId);
}
showMessage('Schaltung erfolgreich gespeichert!');
// Reset form
e.target.reset();
uploadedFiles = [];
document.getElementById('uploadedFiles').innerHTML = '';
hideTarifoptionen(); // Hide tarifoptionen section
const today = new Date().toISOString().split('T')[0];
document.getElementById('eingabeDatum').value = today;
// Reload overview and provisions if needed
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');
const tbody = document.querySelector('#schaltungenTable tbody');
tbody.innerHTML = '';
schaltungen.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 || '')} |
|
|
`;
// Wire delegated handlers so we don't need inline onclick (avoids JS-injection via names).
row.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(row);
});
} catch (error) {
console.error('Fehler beim Laden der Schaltungen:', error);
}
}
// 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) {
modalBody.innerHTML = `
📄
Keine Dokumente vorhanden
Für diese Schaltung wurden noch keine Dokumente hochgeladen.
`;
} else {
documents.forEach(doc => {
const docItem = document.createElement('div');
docItem.className = 'document-item';
// Get file extension for icon
const fileExt = doc.original_filename.split('.').pop().toLowerCase();
let icon = '📄';
if (['pdf'].includes(fileExt)) icon = '📋';
else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) icon = '🖼️';
else if (['doc', 'docx'].includes(fileExt)) icon = '📝';
// Format file size
const fileSize = doc.filesize ? `${(doc.filesize / 1024).toFixed(1)} KB` : 'Unbekannt';
// Format upload date
const uploadDate = doc.uploaded_at ? formatDate(doc.uploaded_at) : 'Unbekannt';
const showView = fileExt === 'pdf';
const safeName = escapeHtml(doc.original_filename);
docItem.innerHTML = `
${icon}
${safeName}
Größe: ${escapeHtml(fileSize)} • Hochgeladen: ${escapeHtml(uploadDate)}
${showView ? `` : ''}
`;
docItem.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(docItem);
});
}
// Setup file upload handler for additional documents
const fileInput = document.getElementById('additionalDocumentUpload');
fileInput.onchange = function(event) {
uploadAdditionalDocuments(schaltungId, event.target.files);
};
modal.classList.add('show');
} 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.remove('show');
}
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 '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 tr = document.createElement('tr');
tr.id = `tarif-item-${t.id}`;
tr.innerHTML = `
${t.id} |
${escapeHtml(t.anbieter_name || 'Unbekannt')} |
${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 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;
}
// Validation for provision euro
if (provisionEuro && parseFloat(provisionEuro) < 0) {
showMessage('Provision kann nicht negativ sein', 'error');
return;
}
try {
const requestData = {
name,
anbieter_id: anbieterId,
provision_euro: provisionEuro ? parseFloat(provisionEuro) : 0,
beschreibung: beschreibung || null
};
await apiRequest('/api/tarife', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
});
// Reset form
document.getElementById('newTarif').value = '';
document.getElementById('tarifAnbieter').value = '';
document.getElementById('tarifProvisionBetrag').value = '';
document.getElementById('tarifBeschreibung').value = '';
showMessage('Tarif erfolgreich hinzugefügt');
// Reload data
await loadTarifeAdmin();
await loadTarifeForProvision();
} catch (error) {
console.error('Fehler beim Hinzufügen des Tarifs:', error);
showMessage('Fehler beim Hinzufügen des Tarifs', 'error');
}
}
// 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 {
// Get all tariffs from the provider
const anbieter = await apiRequest('/api/anbieter');
let tariff = null;
for (const a of anbieter) {
const tarife = await apiRequest(`/api/tarife/${a.id}`);
tariff = tarife.find(t => t.id == id);
if (tariff) break;
}
if (!tariff) {
showMessage('Tarif nicht gefunden', 'error');
return;
}
// Fill modal with tariff data
document.getElementById('editTariffId').value = tariff.id;
document.getElementById('editTariffName').value = tariff.name;
document.getElementById('editTariffProvisionBetrag').value = tariff.provision_euro || '';
// Show modal
document.getElementById('editTariffModal').style.display = 'block';
// Setup form submission
document.getElementById('editTariffForm').onsubmit = async (e) => {
e.preventDefault();
const formData = {
name: document.getElementById('editTariffName').value.trim(),
provision_euro: document.getElementById('editTariffProvisionBetrag').value ? parseFloat(document.getElementById('editTariffProvisionBetrag').value) : 0
};
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;
tbody.innerHTML = '';
if (schaltungen.length === 0) {
// Show "no results" message
const row = document.createElement('tr');
row.innerHTML = `
📋 Keine Schaltungen entsprechen den gewählten Filterkriterien
|
`;
tbody.appendChild(row);
return;
}
schaltungen.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 || '')} |
|
|
`;
row.querySelectorAll('button[data-filt-action]').forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.dataset.filtAction;
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(row);
});
}
// 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 = `
`;
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 = `
`;
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
}
}