浏览代码

JS und HTML Module erstellt um Code zu vereinfachen

Leonard Schulz 1 年之前
父节点
当前提交
9e5a5a8b6f

+ 184 - 0
src/main/resources/static/js/ajax-handlers.js

@@ -0,0 +1,184 @@
+// AJAX-Funktionen für Vorgang- und Zeitraumauswahl
+
+// AJAX-Funktion für Vorgangsauswahl - MIT DEBUG LOGGING
+function changeLeftVorgang(newLeftVorgangId) {
+  console.log('🚀 changeLeftVorgang aufgerufen mit:', newLeftVorgangId);
+  
+  const currentLeftVorgangId = document.getElementById('leftVorgangId').value;
+  console.log('📊 Aktueller leftVorgangId:', currentLeftVorgangId);
+  
+  // Prüfen ob bereits der ausgewählte Vorgang
+  if (currentLeftVorgangId === newLeftVorgangId) {
+    console.log('⚠️ Gleicher Vorgang bereits ausgewählt, überspringe');
+    return; // Nichts zu tun
+  }
+
+  // Loading-Zustand anzeigen
+  showLoadingState();
+  console.log('⏳ Loading-Zustand angezeigt');
+  
+  // Parameter sammeln
+  const leftJson = document.getElementById('leftJson').value;
+  const rightJson = document.getElementById('rightJson').value;
+  const rightZeitraumId = document.getElementById('rightZeitraumId').value;
+  const personId = document.getElementById('currentPersonId').value;
+  
+  console.log('📋 Request Parameter:');
+  console.log('  leftJson length:', leftJson ? leftJson.length : 'null');
+  console.log('  rightJson length:', rightJson ? rightJson.length : 'null');
+  console.log('  rightZeitraumId:', rightZeitraumId);
+  console.log('  personId:', personId);
+  console.log('  newLeftVorgangId:', newLeftVorgangId);
+  
+  // AJAX-Request senden
+  const formData = new FormData();
+  formData.append('leftJson', leftJson);
+  formData.append('rightJson', rightJson);
+  formData.append('rightZeitraumId', rightZeitraumId);
+  formData.append('personId', personId);
+  formData.append('newLeftVorgangId', newLeftVorgangId);
+
+  console.log('🌐 Sende AJAX-Request an /api/vorgang-change');
+  
+  fetch('/api/vorgang-change', {
+    method: 'POST',
+    body: formData
+  })
+  .then(response => {
+    console.log('📡 Response erhalten:', response.status, response.statusText);
+    if (!response.ok) {
+      throw new Error('Netzwerk-Antwort war nicht ok: ' + response.status);
+    }
+    return response.json();
+  })
+  .then(data => {
+    console.log('✅ Erfolgreiche Antwort:', data);
+    // Erfolgreiche Antwort verarbeiten
+    updateAfterVorgangChange(data);
+    hideLoadingState();
+    console.log('🔄 UI erfolgreich aktualisiert');
+  })
+  .catch(error => {
+    console.error('❌ Fehler beim Ändern des Vorgangs:', error);
+    hideLoadingState();
+    alert('Fehler beim Ändern des Vorgangs: ' + error.message + '. Bitte versuchen Sie es erneut.');
+  });
+}
+
+// AJAX-Funktion für Zeitraumauswahl - MIT DEBUG LOGGING  
+function changeLeftZeitraum(newLeftZeitraumId) {
+  console.log('🚀 changeLeftZeitraum aufgerufen mit:', newLeftZeitraumId);
+  
+  const currentLeftZeitraumId = document.getElementById('leftZeitraumId').value;
+  console.log('📊 Aktueller leftZeitraumId:', currentLeftZeitraumId);
+  
+  // Prüfen ob bereits der ausgewählte Zeitraum
+  if (currentLeftZeitraumId === newLeftZeitraumId) {
+    console.log('⚠️ Gleicher Zeitraum bereits ausgewählt, überspringe');
+    return; // Nichts zu tun
+  }
+
+  // Loading-Zustand anzeigen
+  showLoadingState();
+  console.log('⏳ Loading-Zustand angezeigt');
+  
+  // AJAX-Request senden
+  const formData = new FormData();
+  formData.append('leftJson', document.getElementById('leftJson').value);
+  formData.append('rightJson', document.getElementById('rightJson').value);
+  formData.append('leftVorgangId', document.getElementById('leftVorgangId').value);
+  formData.append('rightZeitraumId', document.getElementById('rightZeitraumId').value);
+  formData.append('personId', document.getElementById('currentPersonId').value);
+  formData.append('newLeftZeitraumId', newLeftZeitraumId);
+
+  console.log('🌐 Sende AJAX-Request an /api/zeitraum-change');
+
+  fetch('/api/zeitraum-change', {
+    method: 'POST',
+    body: formData
+  })
+  .then(response => {
+    console.log('📡 Response erhalten:', response.status, response.statusText);
+    if (!response.ok) {
+      throw new Error('Netzwerk-Antwort war nicht ok: ' + response.status);
+    }
+    return response.json();
+  })
+  .then(data => {
+    console.log('✅ Erfolgreiche Antwort:', data);
+    // Erfolgreiche Antwort verarbeiten
+    updateAfterZeitraumChange(data);
+    hideLoadingState();
+    console.log('🔄 UI erfolgreich aktualisiert');
+  })
+  .catch(error => {
+    console.error('❌ Fehler beim Ändern des Zeitraums:', error);
+    hideLoadingState();
+    alert('Fehler beim Ändern des Zeitraums: ' + error.message + '. Bitte versuchen Sie es erneut.');
+  });
+}
+
+// AJAX-Funktion für Personenauswahl
+function selectPersonAjax(personId) {
+  const currentPersonId = document.getElementById('currentPersonId');
+  
+  // Prüfen ob bereits die ausgewählte Person
+  if (currentPersonId.value === personId) {
+    return; // Nichts zu tun
+  }
+
+  // Loading-Zustand anzeigen
+  const detailsTitle = document.getElementById('person-details-title');
+  const originalTitle = detailsTitle.innerHTML;
+  detailsTitle.innerHTML = 'Details werden geladen...';
+  
+  // AJAX-Request senden
+  const formData = new FormData();
+  formData.append('leftJson', document.getElementById('leftJson').value);
+  formData.append('rightJson', document.getElementById('rightJson').value);
+  formData.append('leftVorgangId', document.getElementById('leftVorgangId').value);
+  formData.append('leftZeitraumId', document.getElementById('leftZeitraumId').value);
+  formData.append('rightZeitraumId', document.getElementById('rightZeitraumId').value);
+  formData.append('personId', personId);
+
+  fetch('/api/person-details', {
+    method: 'POST',
+    body: formData
+  })
+  .then(response => {
+    if (!response.ok) {
+      throw new Error('Netzwerk-Antwort war nicht ok');
+    }
+    return response.json();
+  })
+  .then(data => {
+    // Erfolgreiche Antwort verarbeiten
+    updatePersonDetails(data);
+    
+    // Aktuelle Person aktualisieren
+    currentPersonId.value = personId;
+    
+    // Person-Selected-Klasse aktualisieren
+    updateSelectedPersonRow(personId);
+    
+    // Fokus beibehalten falls möglich
+    const activeElement = document.activeElement;
+    if (activeElement && activeElement.closest('.person-row')) {
+      activeElement.focus();
+    }
+  })
+  .catch(error => {
+    console.error('Fehler beim Laden der Personendetails:', error);
+    detailsTitle.innerHTML = originalTitle;
+    alert('Fehler beim Laden der Personendetails. Bitte versuchen Sie es erneut.');
+  });
+}
+
+function showLoadingState() {
+  const detailsTitle = document.getElementById('person-details-title');
+  detailsTitle.innerHTML = 'Daten werden geladen...';
+}
+
+function hideLoadingState() {
+  // Loading-Zustand wird durch updatePersonDetails überschrieben
+}

+ 32 - 0
src/main/resources/static/js/app.js

@@ -0,0 +1,32 @@
+// Hauptinitialisierung der Anwendung
+
+document.addEventListener('DOMContentLoaded', function() {
+  // Initialisiere das Farbschema
+  initializeColorScheme();
+  
+  // Initialisiere Farbschema Event Listeners
+  initializeColorSchemeEventListeners();
+  
+  // Add click event listeners to all person rows - jetzt mit AJAX
+  const personRows = document.querySelectorAll('.person-row');
+  personRows.forEach(function(row) {
+    row.addEventListener('click', function(event) {
+      // Prüfen ob das Event von einem Expand-Button kommt
+      if (event.target.classList.contains('expand-btn') || event.target.closest('.expand-btn')) {
+        return; // Button hat sein eigenes Click-Event
+      }
+      
+      const personId = this.dataset.personId;
+      if (personId) {
+        selectPersonAjax(personId);
+      }
+    });
+  });
+  
+  // Debug-Info ausgeben
+  console.log('🔧 DOM geladen - Debug Info:');
+  console.log('  leftVorgangSelect:', document.getElementById('leftVorgangSelect'));
+  console.log('  leftZeitraumSelect:', document.getElementById('leftZeitraumSelect'));
+  console.log('  leftVorgangId (hidden):', document.getElementById('leftVorgangId'));
+  console.log('  leftZeitraumId (hidden):', document.getElementById('leftZeitraumId'));
+});

+ 100 - 0
src/main/resources/static/js/color-scheme.js

@@ -0,0 +1,100 @@
+// Farbwahl-Funktionen
+
+function toggleColorPicker() {
+  const popup = document.getElementById('colorPickerPopup');
+  const button = document.querySelector('.color-picker-toggle');
+  const isHidden = popup.classList.contains('hidden');
+  
+  popup.classList.toggle('hidden');
+  button.setAttribute('aria-expanded', !isHidden);
+  
+  if (!isHidden) {
+    // Focus the select when opening
+    document.getElementById('colorSchemeSelect').focus();
+  }
+}
+
+function updateColorNames(scheme) {
+  const colorNames = {
+    'standard': { added: 'grün', removed: 'rot', changed: 'gelb' },
+    'alternative': { added: 'blau', removed: 'pink', changed: 'orange' }
+  };
+  
+  const names = colorNames[scheme] || colorNames['standard'];
+  
+  document.getElementById('addedColorName').textContent = names.added;
+  document.getElementById('removedColorName').textContent = names.removed;
+  document.getElementById('changedColorName').textContent = names.changed;
+}
+
+function changeColorScheme(scheme) {
+  const html = document.documentElement;
+  
+  // Entferne alle existierenden Farbschema-Attribute
+  html.removeAttribute('data-color-scheme');
+  
+  // Setze das neue Farbschema (außer für "standard")
+  if (scheme !== 'standard') {
+    html.setAttribute('data-color-scheme', scheme);
+  }
+  
+  // Aktualisiere die Farbnamen in der Legende
+  updateColorNames(scheme);
+  
+  // Speichere die Auswahl im Cookie (365 Tage gültig)
+  setCookie('colorScheme', scheme, 365);
+  
+  // Schließe das Popup nach der Auswahl
+  const popup = document.getElementById('colorPickerPopup');
+  const button = document.querySelector('.color-picker-toggle');
+  popup.classList.add('hidden');
+  button.setAttribute('aria-expanded', 'false');
+}
+
+function initializeColorScheme() {
+  // Lade das gespeicherte Farbschema aus dem Cookie (Standard: "standard" = Ihre ursprünglichen Farben)
+  const savedScheme = getCookie('colorScheme') || 'standard';
+  
+  // Setze das Farbschema
+  const html = document.documentElement;
+  if (savedScheme !== 'standard') {
+    html.setAttribute('data-color-scheme', savedScheme);
+  }
+  
+  // Aktualisiere die Farbnamen
+  updateColorNames(savedScheme);
+  
+  // Setze den Wert in der Dropdown-Box
+  const select = document.getElementById('colorSchemeSelect');
+  if (select) {
+    select.value = savedScheme;
+  }
+}
+
+// Event Listeners für Farbwahl
+function initializeColorSchemeEventListeners() {
+  // Escape key support for popup
+  document.addEventListener('keydown', function(event) {
+    if (event.key === 'Escape') {
+      const popup = document.getElementById('colorPickerPopup');
+      const button = document.querySelector('.color-picker-toggle');
+      if (!popup.classList.contains('hidden')) {
+        popup.classList.add('hidden');
+        button.setAttribute('aria-expanded', 'false');
+        button.focus();
+      }
+    }
+  });
+
+  // Schließe das Popup wenn außerhalb geklickt wird
+  document.addEventListener('click', function(event) {
+    const container = document.querySelector('.color-picker-container');
+    const popup = document.getElementById('colorPickerPopup');
+    const button = document.querySelector('.color-picker-toggle');
+    
+    if (container && !container.contains(event.target)) {
+      popup.classList.add('hidden');
+      button.setAttribute('aria-expanded', 'false');
+    }
+  });
+}

+ 17 - 0
src/main/resources/static/js/cookie-utils.js

@@ -0,0 +1,17 @@
+// Cookie Verwaltung
+function setCookie(name, value, days) {
+  const expires = new Date();
+  expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
+  document.cookie = name + '=' + value + ';expires=' + expires.toUTCString() + ';path=/';
+}
+
+function getCookie(name) {
+  const nameEQ = name + '=';
+  const ca = document.cookie.split(';');
+  for(let i = 0; i < ca.length; i++) {
+    let c = ca[i];
+    while (c.charAt(0) === ' ') c = c.substring(1, c.length);
+    if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
+  }
+  return null;
+}

+ 89 - 0
src/main/resources/static/js/interactions.js

@@ -0,0 +1,89 @@
+// Interaktivitätsfunktionen
+
+// Person Details Toggle-Funktionalität
+function togglePersonDetails(button, personId) {
+  const detailsRow = document.getElementById('person-details-' + personId);
+  const isExpanded = button.getAttribute('aria-expanded') === 'true';
+  
+  if (isExpanded) {
+    // Zuklappen
+    detailsRow.classList.add('hidden');
+    button.setAttribute('aria-expanded', 'false');
+    button.querySelector('span').textContent = '▶';
+    button.setAttribute('aria-label', button.getAttribute('aria-label').replace('ausblenden', 'anzeigen'));
+  } else {
+    // Aufklappen
+    detailsRow.classList.remove('hidden');
+    button.setAttribute('aria-expanded', 'true');
+    button.querySelector('span').textContent = '▼';
+    button.setAttribute('aria-label', button.getAttribute('aria-label').replace('anzeigen', 'ausblenden'));
+  }
+}
+
+// New function to handle button clicks using data attribute
+function togglePersonDetailsFromButton(button) {
+  const personId = button.getAttribute('data-person-id');
+  if (personId) {
+    togglePersonDetails(button, personId);
+  }
+}
+
+// Keyboard support for person details toggle
+function handleExpandKeyPress(event, button, personId) {
+  if (event.key === 'Enter' || event.key === ' ') {
+    event.preventDefault();
+    event.stopPropagation(); // Verhindert, dass die Zeile auch geklickt wird
+    togglePersonDetails(button, personId);
+  }
+}
+
+// New function to handle keyboard events using data attribute
+function handleExpandKeyPressFromButton(event, button) {
+  if (event.key === 'Enter' || event.key === ' ') {
+    event.preventDefault();
+    event.stopPropagation(); // Verhindert, dass die Zeile auch geklickt wird
+    const personId = button.getAttribute('data-person-id');
+    if (personId) {
+      togglePersonDetails(button, personId);
+    }
+  }
+}
+
+// Keyboard support for toggles
+function handleKeyPress(event, sectionId) {
+  if (event.key === 'Enter' || event.key === ' ') {
+    event.preventDefault();
+    toggleSection(sectionId);
+  }
+}
+
+// Keyboard support for table rows
+function handleRowKeyPress(event, row) {
+  if (event.key === 'Enter' || event.key === ' ') {
+    // Prüfen ob das Event von einem Expand-Button kommt
+    if (event.target.classList.contains('expand-btn') || event.target.closest('.expand-btn')) {
+      return; // Lass den expand button sein eigenes Event handhaben
+    }
+    event.preventDefault();
+    row.click();
+  }
+}
+
+// Toggle-Funktion für ausklappbare Sektionen
+function toggleSection(sectionId) {
+  const content = document.getElementById(sectionId + '-content');
+  const icon = document.getElementById(sectionId + '-icon');
+  const header = content.parentElement.querySelector('.section-header');
+  
+  if (content.classList.contains('collapsed')) {
+    content.classList.remove('collapsed');
+    icon.classList.remove('collapsed');
+    icon.textContent = '▼';
+    header.setAttribute('aria-expanded', 'true');
+  } else {
+    content.classList.add('collapsed');
+    icon.classList.add('collapsed');
+    icon.textContent = '▶';
+    header.setAttribute('aria-expanded', 'false');
+  }
+}

+ 253 - 0
src/main/resources/static/js/ui-updater.js

@@ -0,0 +1,253 @@
+// UI-Update-Funktionen
+
+function updateAfterVorgangChange(data) {
+  console.log('🔄 updateAfterVorgangChange aufgerufen mit:', data);
+  
+  // Hidden fields aktualisieren
+  document.getElementById('leftVorgangId').value = data.leftVorgangId;
+  document.getElementById('leftZeitraumId').value = data.leftZeitraumId;
+  document.getElementById('currentPersonId').value = data.personId;
+  
+  // Vorgang-Dropdown aktualisieren (falls erforderlich)
+  const vorgangSelect = document.getElementById('leftVorgangSelect');
+  if (vorgangSelect.value !== data.leftVorgangId) {
+    vorgangSelect.value = data.leftVorgangId;
+  }
+  
+  // Zeitraum-Dropdown aktualisieren
+  const zeitraumSelect = document.getElementById('leftZeitraumSelect');
+  zeitraumSelect.innerHTML = '';
+  data.leftZeitraeume.forEach(zeitraum => {
+    const option = document.createElement('option');
+    option.value = zeitraum.id;
+    option.textContent = zeitraum.beginn + ' - ' + zeitraum.ende;
+    if (zeitraum.id === data.leftZeitraumId) {
+      option.selected = true;
+    }
+    zeitraumSelect.appendChild(option);
+  });
+  
+  console.log('📋 Zeitraum-Dropdown aktualisiert mit', data.leftZeitraeume.length, 'Einträgen');
+  
+  // Haushaltsmitglieder-Tabelle aktualisieren
+  updateHaushaltsmitgliederTable(data.mitgliederRows, data.personId);
+  
+  // Personendetails aktualisieren
+  updatePersonDetails(data);
+  
+  console.log('✅ updateAfterVorgangChange abgeschlossen');
+}
+
+function updateAfterZeitraumChange(data) {
+  console.log('🔄 updateAfterZeitraumChange aufgerufen mit:', data);
+  
+  // Hidden field aktualisieren
+  document.getElementById('leftZeitraumId').value = data.leftZeitraumId;
+  document.getElementById('currentPersonId').value = data.personId;
+  
+  // Zeitraum-Dropdown aktualisieren (falls erforderlich)
+  const zeitraumSelect = document.getElementById('leftZeitraumSelect');
+  if (zeitraumSelect.value !== data.leftZeitraumId) {
+    zeitraumSelect.value = data.leftZeitraumId;
+  }
+  
+  // Haushaltsmitglieder-Tabelle aktualisieren
+  updateHaushaltsmitgliederTable(data.mitgliederRows, data.personId);
+  
+  // Personendetails aktualisieren
+  updatePersonDetails(data);
+  
+  console.log('✅ updateAfterZeitraumChange abgeschlossen');
+}
+
+function updateHaushaltsmitgliederTable(mitgliederRows, selectedPersonId) {
+  const table = document.getElementById('haushaltsmitglieder-table');
+  const tbody = table.querySelector('tbody');
+  
+  // Tbody leeren
+  tbody.innerHTML = '';
+  
+  // Neue Zeilen hinzufügen
+  mitgliederRows.forEach(row => {
+    // Hauptzeile
+    const tr = document.createElement('tr');
+    tr.className = 'person-row ' + (row.rowClass || '') + (row.personId === selectedPersonId ? ' person-selected' : '');
+    tr.setAttribute('data-person-id', row.personId);
+    tr.setAttribute('role', 'button');
+    tr.setAttribute('tabindex', '0');
+    tr.setAttribute('aria-label', 'Person auswählen: ' + (row.nameHtml || '').replace(/<[^>]*>/g, ''));
+    tr.setAttribute('onkeypress', 'handleRowKeyPress(event, this)');
+    
+    // Spalten hinzufügen
+    const expandTd = document.createElement('td');
+    expandTd.className = 'expand-col';
+    expandTd.innerHTML = `
+      <button type="button" 
+              class="expand-btn" 
+              data-person-id="${row.personId}"
+              onclick="togglePersonDetailsFromButton(this)"
+              onkeypress="handleExpandKeyPressFromButton(event, this)"
+              aria-expanded="false"
+              aria-controls="person-details-${row.personId}"
+              aria-label="Details für ${(row.nameHtml || '').replace(/<[^>]*>/g, '')} anzeigen">
+        <span aria-hidden="true">▶</span>
+      </button>
+    `;
+    tr.appendChild(expandTd);
+    
+    const symbolTd = document.createElement('td');
+    symbolTd.className = 'sym ' + (row.symbolClass || '');
+    symbolTd.textContent = row.symbol || '';
+    const ariaLabel = row.symbol === '+' ? 'Hinzugefügt' : 
+                     (row.symbol === '-' ? 'Gelöscht' : 
+                     (row.symbol === '!' ? 'Geändert' : 'Unverändert'));
+    symbolTd.setAttribute('aria-label', ariaLabel);
+    tr.appendChild(symbolTd);
+    
+    // Weitere Spalten
+    const columns = ['nameHtml', 'blocknameHtml', 'geburtsdatumHtml', 'sterbedatumHtml', 
+                    'familienstandHtml', 'geschlechtHtml', 'anredeHtml', 'verwandtschaftHtml',
+                    'ausschlussgrundHtml', 'sozialstellungHtml', 'antragstellerHtml', 
+                    'zahlungsempfaengerHtml', 'aufenthaltstitelEndeHtml'];
+    
+    columns.forEach(column => {
+      const td = document.createElement('td');
+      td.innerHTML = row[column] || '';
+      tr.appendChild(td);
+    });
+    
+    tbody.appendChild(tr);
+    
+    // Detailzeile hinzufügen
+    const detailTr = document.createElement('tr');
+    detailTr.id = 'person-details-' + row.personId;
+    detailTr.className = 'person-details-row hidden';
+    detailTr.setAttribute('aria-labelledby', 'person-details-label-' + row.personId);
+    
+    const detailTd = document.createElement('td');
+    detailTd.setAttribute('colspan', '15');
+    detailTd.className = 'details-cell';
+    detailTd.innerHTML = `
+      <div class="details-container">
+        <table class="details-table">
+          <tbody>
+            <tr>
+              <td class="detail-label">Titel:</td>
+              <td>${row.titelHtml || ''}</td>
+              <td class="detail-label">Geburtsort:</td>
+              <td>${row.geburtsortHtml || ''}</td>
+              <td class="detail-label">Telefon:</td>
+              <td>${row.telefonHtml || ''}</td>
+              <td class="detail-label">Aktenzeichen Soz.:</td>
+              <td>${row.aktenzeichenSozialHtml || ''}</td>
+            </tr>
+            <tr>
+              <td class="detail-label">Geburtsname:</td>
+              <td>${row.geburtsnameHtml || ''}</td>
+              <td class="detail-label">Alter Name:</td>
+              <td>${row.alterNameHtml || ''}</td>
+              <td class="detail-label">E-Mail:</td>
+              <td>${row.emailHtml || ''}</td>
+              <td class="detail-label">Postabrutionsnummer:</td>
+              <td>${row.postabrutionsnummerHtml || ''}</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    `;
+    detailTr.appendChild(detailTd);
+    tbody.appendChild(detailTr);
+  });
+  
+  // Event Listeners für neue Zeilen hinzufügen
+  const newPersonRows = tbody.querySelectorAll('.person-row');
+  newPersonRows.forEach(function(row) {
+    row.addEventListener('click', function(event) {
+      // Prüfen ob das Event von einem Expand-Button kommt
+      if (event.target.classList.contains('expand-btn') || event.target.closest('.expand-btn')) {
+        return; // Button hat sein eigenes Click-Event
+      }
+      
+      const personId = this.dataset.personId;
+      if (personId) {
+        selectPersonAjax(personId);
+      }
+    });
+  });
+}
+
+function updatePersonDetails(data) {
+  // Titel aktualisieren
+  const detailsTitle = document.getElementById('person-details-title');
+  detailsTitle.innerHTML = 'Details zu ' + (data.personLabel && data.personLabel !== '(keine Person)' ? data.personLabel : '');
+  
+  // Einkommen-Tabelle aktualisieren
+  updateTable('einkommen-table', data.einkommenZeilen, [
+    'symbol', 'artHtml', 'artZusatzHtml', 'faktorHtml', 'betragHtml', 
+    'zuschlagHtml', 'abschlagHtml', 'werbungskostenHtml'
+  ]);
+  
+  // Pauschale Abzüge aktualisieren
+  const pauschaleZeilen = data.freibetragZeilen.filter(z => z.typ === 'P');
+  updateTable('pauschale-table', pauschaleZeilen, [
+    'symbol', 'artHtml', 'faktorHtml', 'betragHtml'
+  ]);
+  
+  // Freibeträge aktualisieren
+  const freibetraege = data.freibetragZeilen.filter(z => z.typ === 'F');
+  updateTable('freibetraege-table', freibetraege, [
+    'symbol', 'artHtml', 'faktorHtml', 'betragHtml', 'fristHtml'
+  ]);
+  
+  // Kinderbetreuung aktualisieren
+  updateTable('kinderbetreuung-table', data.kitaZeilen, [
+    'symbol', 'kindNameHtml', 'betragHtml'
+  ]);
+}
+
+function updateTable(tableId, data, columns) {
+  const table = document.getElementById(tableId);
+  const tbody = table.querySelector('tbody');
+  
+  // Tbody leeren
+  tbody.innerHTML = '';
+  
+  // Neue Zeilen hinzufügen
+  data.forEach(row => {
+    const tr = document.createElement('tr');
+    tr.className = row.rowClass || '';
+    
+    columns.forEach((column, index) => {
+      const td = document.createElement('td');
+      
+      if (index === 0) { // Symbol-Spalte
+        td.className = 'sym ' + (row.symbolClass || '');
+        td.textContent = row.symbol || '';
+        const ariaLabel = row.symbol === '+' ? 'Hinzugefügt' : 
+                         (row.symbol === '-' ? 'Gelöscht' : 
+                         (row.symbol === '!' ? 'Geändert' : 'Unverändert'));
+        td.setAttribute('aria-label', ariaLabel);
+      } else {
+        td.innerHTML = row[column] || '';
+      }
+      
+      tr.appendChild(td);
+    });
+    
+    tbody.appendChild(tr);
+  });
+}
+
+function updateSelectedPersonRow(personId) {
+  // Alle person-selected Klassen entfernen
+  const allPersonRows = document.querySelectorAll('.person-row');
+  allPersonRows.forEach(row => {
+    row.classList.remove('person-selected');
+  });
+  
+  // Neue person-selected Klasse setzen
+  const selectedRow = document.querySelector(`[data-person-id="${personId}"]`);
+  if (selectedRow) {
+    selectedRow.classList.add('person-selected');
+  }
+}

+ 75 - 0
src/main/resources/templates/fragments/header.html

@@ -0,0 +1,75 @@
+<!-- Header Fragment für DIWO Vergleich -->
+<header class="diwo-header" role="banner" xmlns:th="http://www.thymeleaf.org" th:fragment="header">
+    <div class="header-row-1">
+      <div class="kunde-logo">
+        <div th:if="${activeKunde != null and !#strings.isEmpty(activeKunde)}" class="kunde-logo-container">
+          <img th:src="@{'/images/' + ${#strings.toLowerCase(activeKunde)} + '.jpg'}" 
+               th:alt="${activeKunde != null and !#strings.isEmpty(activeKunde) ? activeKunde + ' Logo' : 'Kunden-Logo'}" 
+               class="kunde-logo-image"
+               th:onerror="'this.style.display=\\'none\\''"/>
+        </div>
+      </div>
+      <div class="diwo-title">
+        <h1>DIWO</h1>
+        <h2>Vergleich</h2>
+      </div>
+      <div class="page-title" role="heading" aria-level="2">Haushaltsmitglieder/Einkommen</div>
+    </div>
+    
+    <div class="header-row-2">
+      <div class="aktenzeichen" th:if="${stammakteRight != null and !#strings.isEmpty(stammakteRight.aktenzeichen)}" role="contentinfo" aria-label="Aktenzeichen">
+        <span th:text="${stammakteRight.aktenzeichen}">01/01/119979</span>
+      </div>
+      <div class="aktenzeichen" th:if="${(stammakteRight == null or #strings.isEmpty(stammakteRight.aktenzeichen)) and stammvorgangRight != null}" role="contentinfo" aria-label="Aktenzeichen">
+        <span th:text="${stammvorgangRight.id}">01/01/119979</span>
+      </div>
+      
+      <div class="antragsteller-info" th:if="${stammakteRight != null}" role="contentinfo" aria-label="Antragsteller-Informationen">
+        <span th:if="${!#strings.isEmpty(stammakteRight.antragstellername)}" th:text="${stammakteRight.antragstellername}">Müller, Tobias</span>
+        <span th:if="${!#strings.isEmpty(stammakteRight.antragstellerGeburtsdatum)}"> - <span th:text="${stammakteRight.antragstellerGeburtsdatum}">01.01.2000</span></span>
+      </div>
+      
+      <div class="location-info" th:if="${stammakteRight != null and !#strings.isEmpty(stammakteRight.anschrift)}" role="contentinfo" aria-label="Anschrift">
+        <span th:text="${stammakteRight.anschrift}">Unter den Linden 1, 10117 Berlin</span>
+      </div>
+    </div>
+    
+    <div class="header-row-3">
+      <div class="vorgang-info" th:if="${stammvorgangRight != null}" role="contentinfo" aria-label="Vorgang in Bearbeitung">
+        <span>Vorgang in der Bearbeitung: </span>
+        <span th:if="${!#strings.isEmpty(stammvorgangRight.vorgangstyp)}" th:text="${stammvorgangRight.vorgangstyp}">Weiterleistungsantrag</span>
+        <span th:if="${#strings.isEmpty(stammvorgangRight.vorgangstyp)}">-</span>
+        <span th:if="${!#strings.isEmpty(stammvorgangRight.vorgangsstatus)}">
+          (Status: <span th:text="${stammvorgangRight.vorgangsstatus}">aktiv</span>)
+        </span>
+      </div>
+      
+      <div class="vergleich-controls">
+        <div class="vergleich-controls-wrapper">
+          <!-- Vorgangsauswahl (links/alt) - jetzt mit AJAX -->
+          <div class="vorgang-form" role="form" aria-label="Vorgangsauswahl">
+            <div class="control-group">
+              <label for="leftVorgangSelect">Vergleichsvorgang:</label>
+              <select name="leftVorgangId" id="leftVorgangSelect" class="vorgang-select" onchange="changeLeftVorgang(this.value)">
+                <option th:each="v : ${leftCandidates}" th:value="${v.id}"
+                        th:selected="${v.id == leftVorgangId}"
+                        th:text="${v.vorgangstyp + (!#strings.isEmpty(v.vorgangstyp) ? ' (' + v.id + ')' : v.id)}"></option>
+              </select>
+            </div>
+          </div>
+          
+          <!-- Zeitraumauswahl (links/alt) - jetzt mit AJAX -->
+          <div class="zeitraum-form" role="form" aria-label="Zeitraumauswahl">
+            <div class="control-group">
+              <label for="leftZeitraumSelect">Zeitraum:</label>
+              <select name="leftZeitraumId" id="leftZeitraumSelect" class="zeitraum-select" onchange="changeLeftZeitraum(this.value)">
+                <option th:each="z : ${leftZeitraeumeOfSelected}" th:value="${z.id}"
+                        th:selected="${z.id == leftZeitraumId}"
+                        th:text="${z.beginn} + ' - ' + ${z.ende}"></option>
+              </select>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+</header>

+ 36 - 0
src/main/resources/templates/fragments/legend.html

@@ -0,0 +1,36 @@
+<!-- Vergleichslegende Fragment -->
+<section class="vergleich-legende" role="complementary" aria-labelledby="legende-title" xmlns:th="http://www.thymeleaf.org" th:fragment="legend">
+    <div class="legende-title" id="legende-title">
+      <strong>Legende:</strong>
+    </div>
+    <div class="legende-item">
+      <span class="sym plus" aria-label="Hinzugefügt">+</span> 
+      <span>hinzugefügt (<span id="addedColorName">grün</span>)</span>
+    </div>
+    <div class="legende-spacer" aria-hidden="true">|</div>
+    <div class="legende-item">
+      <span class="sym minus" aria-label="Gelöscht">−</span> 
+      <span>gelöscht (<span id="removedColorName">rot</span>)</span>
+    </div>
+    <div class="legende-spacer" aria-hidden="true">|</div>
+    <div class="legende-item">
+      <span class="sym warn" aria-label="Geändert">!</span> 
+      <span>geändert (<span id="changedColorName">gelb</span>)</span>
+    </div>
+    <div class="legende-spacer" aria-hidden="true">|</div>
+    <div class="legende-item">
+      <span class="selected-indicator-text">ausgewählte Person (blauer Rahmen)</span>
+    </div>
+    
+    <!-- Farbwahl -->
+    <div class="color-picker-container">
+      <button class="color-picker-toggle" onclick="toggleColorPicker()" aria-label="Farbschema-Einstellungen öffnen" aria-expanded="false" aria-controls="colorPickerPopup">⚙️</button>
+      <div class="color-picker-popup hidden" id="colorPickerPopup" role="dialog" aria-labelledby="color-picker-label">
+        <label id="color-picker-label" for="colorSchemeSelect">Farbwahl:</label>
+        <select id="colorSchemeSelect" class="color-scheme-select" onchange="changeColorScheme(this.value)" aria-describedby="color-scheme-hint">
+          <option value="standard">Standard</option>
+          <option value="alternative">Pastell</option>
+        </select>
+      </div>
+    </div>
+</section>

+ 11 - 777
src/main/resources/templates/vergleich.html

@@ -5,120 +5,21 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <title>DIWO Vergleich – Haushaltsmitglieder/Einkommen</title>
     <link rel="stylesheet" href="/styles.css"/>
+    <!-- JavaScript Module -->
+    <script src="/js/cookie-utils.js"></script>
+    <script src="/js/ajax-handlers.js"></script>
+    <script src="/js/ui-updater.js"></script>
+    <script src="/js/interactions.js"></script>
+    <script src="/js/color-scheme.js"></script>
+    <script src="/js/app.js"></script>
 </head>
 <body>
 
-  <header class="diwo-header" role="banner">
-    <div class="header-row-1">
-      <div class="kunde-logo">
-        <div th:if="${activeKunde != null and !#strings.isEmpty(activeKunde)}" class="kunde-logo-container">
-          <img th:src="@{'/images/' + ${#strings.toLowerCase(activeKunde)} + '.jpg'}" 
-               th:alt="${activeKunde != null and !#strings.isEmpty(activeKunde) ? activeKunde + ' Logo' : 'Kunden-Logo'}" 
-               class="kunde-logo-image"
-               th:onerror="'this.style.display=\\'none\\''"/>
-        </div>
-      </div>
-      <div class="diwo-title">
-        <h1>DIWO</h1>
-        <h2>Vergleich</h2>
-      </div>
-      <div class="page-title" role="heading" aria-level="2">Haushaltsmitglieder/Einkommen</div>
-    </div>
-    
-    <div class="header-row-2">
-      <div class="aktenzeichen" th:if="${stammakteRight != null and !#strings.isEmpty(stammakteRight.aktenzeichen)}" role="contentinfo" aria-label="Aktenzeichen">
-        <span th:text="${stammakteRight.aktenzeichen}">01/01/119979</span>
-      </div>
-      <div class="aktenzeichen" th:if="${(stammakteRight == null or #strings.isEmpty(stammakteRight.aktenzeichen)) and stammvorgangRight != null}" role="contentinfo" aria-label="Aktenzeichen">
-        <span th:text="${stammvorgangRight.id}">01/01/119979</span>
-      </div>
-      
-      <div class="antragsteller-info" th:if="${stammakteRight != null}" role="contentinfo" aria-label="Antragsteller-Informationen">
-        <span th:if="${!#strings.isEmpty(stammakteRight.antragstellername)}" th:text="${stammakteRight.antragstellername}">Müller, Tobias</span>
-        <span th:if="${!#strings.isEmpty(stammakteRight.antragstellerGeburtsdatum)}"> - <span th:text="${stammakteRight.antragstellerGeburtsdatum}">01.01.2000</span></span>
-      </div>
-      
-      <div class="location-info" th:if="${stammakteRight != null and !#strings.isEmpty(stammakteRight.anschrift)}" role="contentinfo" aria-label="Anschrift">
-        <span th:text="${stammakteRight.anschrift}">Unter den Linden 1, 10117 Berlin</span>
-      </div>
-    </div>
-    
-    <div class="header-row-3">
-      <div class="vorgang-info" th:if="${stammvorgangRight != null}" role="contentinfo" aria-label="Vorgang in Bearbeitung">
-        <span>Vorgang in der Bearbeitung: </span>
-        <span th:if="${!#strings.isEmpty(stammvorgangRight.vorgangstyp)}" th:text="${stammvorgangRight.vorgangstyp}">Weiterleistungsantrag</span>
-        <span th:if="${#strings.isEmpty(stammvorgangRight.vorgangstyp)}">-</span>
-        <span th:if="${!#strings.isEmpty(stammvorgangRight.vorgangsstatus)}">
-          (Status: <span th:text="${stammvorgangRight.vorgangsstatus}">aktiv</span>)
-        </span>
-      </div>
-      
-      <div class="vergleich-controls">
-        <div class="vergleich-controls-wrapper">
-          <!-- Vorgangsauswahl (links/alt) - jetzt mit AJAX -->
-          <div class="vorgang-form" role="form" aria-label="Vorgangsauswahl">
-            <div class="control-group">
-              <label for="leftVorgangSelect">Vergleichsvorgang:</label>
-              <select name="leftVorgangId" id="leftVorgangSelect" class="vorgang-select" onchange="changeLeftVorgang(this.value)">
-                <option th:each="v : ${leftCandidates}" th:value="${v.id}"
-                        th:selected="${v.id == leftVorgangId}"
-                        th:text="${v.vorgangstyp + (!#strings.isEmpty(v.vorgangstyp) ? ' (' + v.id + ')' : v.id)}"></option>
-              </select>
-            </div>
-          </div>
-          
-          <!-- Zeitraumauswahl (links/alt) - jetzt mit AJAX -->
-          <div class="zeitraum-form" role="form" aria-label="Zeitraumauswahl">
-            <div class="control-group">
-              <label for="leftZeitraumSelect">Zeitraum:</label>
-              <select name="leftZeitraumId" id="leftZeitraumSelect" class="zeitraum-select" onchange="changeLeftZeitraum(this.value)">
-                <option th:each="z : ${leftZeitraeumeOfSelected}" th:value="${z.id}"
-                        th:selected="${z.id == leftZeitraumId}"
-                        th:text="${z.beginn} + ' - ' + ${z.ende}"></option>
-              </select>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </header>
+  <!-- Header-Fragment einbinden -->
+  <div th:replace="~{fragments/header :: header}"></div>
 
-  <!-- Vergleichslegende -->
-  <section class="vergleich-legende" role="complementary" aria-labelledby="legende-title">
-    <div class="legende-title" id="legende-title">
-      <strong>Legende:</strong>
-    </div>
-    <div class="legende-item">
-      <span class="sym plus" aria-label="Hinzugefügt">+</span> 
-      <span>hinzugefügt (<span id="addedColorName">grün</span>)</span>
-    </div>
-    <div class="legende-spacer" aria-hidden="true">|</div>
-    <div class="legende-item">
-      <span class="sym minus" aria-label="Gelöscht">−</span> 
-      <span>gelöscht (<span id="removedColorName">rot</span>)</span>
-    </div>
-    <div class="legende-spacer" aria-hidden="true">|</div>
-    <div class="legende-item">
-      <span class="sym warn" aria-label="Geändert">!</span> 
-      <span>geändert (<span id="changedColorName">gelb</span>)</span>
-    </div>
-    <div class="legende-spacer" aria-hidden="true">|</div>
-    <div class="legende-item">
-      <span class="selected-indicator-text">ausgewählte Person (blauer Rahmen)</span>
-    </div>
-    
-    <!-- Farbwahl -->
-    <div class="color-picker-container">
-      <button class="color-picker-toggle" onclick="toggleColorPicker()" aria-label="Farbschema-Einstellungen öffnen" aria-expanded="false" aria-controls="colorPickerPopup">⚙️</button>
-      <div class="color-picker-popup hidden" id="colorPickerPopup" role="dialog" aria-labelledby="color-picker-label">
-        <label id="color-picker-label" for="colorSchemeSelect">Farbwahl:</label>
-        <select id="colorSchemeSelect" class="color-scheme-select" onchange="changeColorScheme(this.value)" aria-describedby="color-scheme-hint">
-          <option value="standard">Standard</option>
-          <option value="alternative">Pastell</option>
-        </select>
-      </div>
-    </div>
-  </section>
+  <!-- Vergleichslegende-Fragment einbinden -->
+  <div th:replace="~{fragments/legend :: legend}"></div>
 
   <!-- Haushaltsmitglieder - erweiterte Tabelle mit aufklappbaren Zeilen -->
   <main role="main">
@@ -373,672 +274,5 @@
     </div>
   </main>
 
-  <script>
-    // Cookie Verwaltung
-    function setCookie(name, value, days) {
-      const expires = new Date();
-      expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
-      document.cookie = name + '=' + value + ';expires=' + expires.toUTCString() + ';path=/';
-    }
-
-    function getCookie(name) {
-      const nameEQ = name + '=';
-      const ca = document.cookie.split(';');
-      for(let i = 0; i < ca.length; i++) {
-        let c = ca[i];
-        while (c.charAt(0) === ' ') c = c.substring(1, c.length);
-        if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
-      }
-      return null;
-    }
-
-    // Person Details Toggle-Funktionalität
-    function togglePersonDetails(button, personId) {
-      const detailsRow = document.getElementById('person-details-' + personId);
-      const isExpanded = button.getAttribute('aria-expanded') === 'true';
-      
-      if (isExpanded) {
-        // Zuklappen
-        detailsRow.classList.add('hidden');
-        button.setAttribute('aria-expanded', 'false');
-        button.querySelector('span').textContent = '▶';
-        button.setAttribute('aria-label', button.getAttribute('aria-label').replace('ausblenden', 'anzeigen'));
-      } else {
-        // Aufklappen
-        detailsRow.classList.remove('hidden');
-        button.setAttribute('aria-expanded', 'true');
-        button.querySelector('span').textContent = '▼';
-        button.setAttribute('aria-label', button.getAttribute('aria-label').replace('anzeigen', 'ausblenden'));
-      }
-    }
-
-    // New function to handle button clicks using data attribute
-    function togglePersonDetailsFromButton(button) {
-      const personId = button.getAttribute('data-person-id');
-      if (personId) {
-        togglePersonDetails(button, personId);
-      }
-    }
-
-    // Keyboard support for person details toggle
-    function handleExpandKeyPress(event, button, personId) {
-      if (event.key === 'Enter' || event.key === ' ') {
-        event.preventDefault();
-        event.stopPropagation(); // Verhindert, dass die Zeile auch geklickt wird
-        togglePersonDetails(button, personId);
-      }
-    }
-
-    // New function to handle keyboard events using data attribute
-    function handleExpandKeyPressFromButton(event, button) {
-      if (event.key === 'Enter' || event.key === ' ') {
-        event.preventDefault();
-        event.stopPropagation(); // Verhindert, dass die Zeile auch geklickt wird
-        const personId = button.getAttribute('data-person-id');
-        if (personId) {
-          togglePersonDetails(button, personId);
-        }
-      }
-    }
-
-    // AJAX-Funktion für Vorgangsauswahl - MIT DEBUG LOGGING
-    function changeLeftVorgang(newLeftVorgangId) {
-      console.log('🚀 changeLeftVorgang aufgerufen mit:', newLeftVorgangId);
-      
-      const currentLeftVorgangId = document.getElementById('leftVorgangId').value;
-      console.log('📊 Aktueller leftVorgangId:', currentLeftVorgangId);
-      
-      // Prüfen ob bereits der ausgewählte Vorgang
-      if (currentLeftVorgangId === newLeftVorgangId) {
-        console.log('⚠️ Gleicher Vorgang bereits ausgewählt, überspringe');
-        return; // Nichts zu tun
-      }
-
-      // Loading-Zustand anzeigen
-      showLoadingState();
-      console.log('⏳ Loading-Zustand angezeigt');
-      
-      // Parameter sammeln
-      const leftJson = document.getElementById('leftJson').value;
-      const rightJson = document.getElementById('rightJson').value;
-      const rightZeitraumId = document.getElementById('rightZeitraumId').value;
-      const personId = document.getElementById('currentPersonId').value;
-      
-      console.log('📋 Request Parameter:');
-      console.log('  leftJson length:', leftJson ? leftJson.length : 'null');
-      console.log('  rightJson length:', rightJson ? rightJson.length : 'null');
-      console.log('  rightZeitraumId:', rightZeitraumId);
-      console.log('  personId:', personId);
-      console.log('  newLeftVorgangId:', newLeftVorgangId);
-      
-      // AJAX-Request senden
-      const formData = new FormData();
-      formData.append('leftJson', leftJson);
-      formData.append('rightJson', rightJson);
-      formData.append('rightZeitraumId', rightZeitraumId);
-      formData.append('personId', personId);
-      formData.append('newLeftVorgangId', newLeftVorgangId);
-
-      console.log('🌐 Sende AJAX-Request an /api/vorgang-change');
-      
-      fetch('/api/vorgang-change', {
-        method: 'POST',
-        body: formData
-      })
-      .then(response => {
-        console.log('📡 Response erhalten:', response.status, response.statusText);
-        if (!response.ok) {
-          throw new Error('Netzwerk-Antwort war nicht ok: ' + response.status);
-        }
-        return response.json();
-      })
-      .then(data => {
-        console.log('✅ Erfolgreiche Antwort:', data);
-        // Erfolgreiche Antwort verarbeiten
-        updateAfterVorgangChange(data);
-        hideLoadingState();
-        console.log('🔄 UI erfolgreich aktualisiert');
-      })
-      .catch(error => {
-        console.error('❌ Fehler beim Ändern des Vorgangs:', error);
-        hideLoadingState();
-        alert('Fehler beim Ändern des Vorgangs: ' + error.message + '. Bitte versuchen Sie es erneut.');
-      });
-    }
-
-    // AJAX-Funktion für Zeitraumauswahl - MIT DEBUG LOGGING  
-    function changeLeftZeitraum(newLeftZeitraumId) {
-      console.log('🚀 changeLeftZeitraum aufgerufen mit:', newLeftZeitraumId);
-      
-      const currentLeftZeitraumId = document.getElementById('leftZeitraumId').value;
-      console.log('📊 Aktueller leftZeitraumId:', currentLeftZeitraumId);
-      
-      // Prüfen ob bereits der ausgewählte Zeitraum
-      if (currentLeftZeitraumId === newLeftZeitraumId) {
-        console.log('⚠️ Gleicher Zeitraum bereits ausgewählt, überspringe');
-        return; // Nichts zu tun
-      }
-
-      // Loading-Zustand anzeigen
-      showLoadingState();
-      console.log('⏳ Loading-Zustand angezeigt');
-      
-      // AJAX-Request senden
-      const formData = new FormData();
-      formData.append('leftJson', document.getElementById('leftJson').value);
-      formData.append('rightJson', document.getElementById('rightJson').value);
-      formData.append('leftVorgangId', document.getElementById('leftVorgangId').value);
-      formData.append('rightZeitraumId', document.getElementById('rightZeitraumId').value);
-      formData.append('personId', document.getElementById('currentPersonId').value);
-      formData.append('newLeftZeitraumId', newLeftZeitraumId);
-
-      console.log('🌐 Sende AJAX-Request an /api/zeitraum-change');
-
-      fetch('/api/zeitraum-change', {
-        method: 'POST',
-        body: formData
-      })
-      .then(response => {
-        console.log('📡 Response erhalten:', response.status, response.statusText);
-        if (!response.ok) {
-          throw new Error('Netzwerk-Antwort war nicht ok: ' + response.status);
-        }
-        return response.json();
-      })
-      .then(data => {
-        console.log('✅ Erfolgreiche Antwort:', data);
-        // Erfolgreiche Antwort verarbeiten
-        updateAfterZeitraumChange(data);
-        hideLoadingState();
-        console.log('🔄 UI erfolgreich aktualisiert');
-      })
-      .catch(error => {
-        console.error('❌ Fehler beim Ändern des Zeitraums:', error);
-        hideLoadingState();
-        alert('Fehler beim Ändern des Zeitraums: ' + error.message + '. Bitte versuchen Sie es erneut.');
-      });
-    }
-
-    // AJAX-Funktion für Personenauswahl
-    function selectPersonAjax(personId) {
-      const currentPersonId = document.getElementById('currentPersonId');
-      
-      // Prüfen ob bereits die ausgewählte Person
-      if (currentPersonId.value === personId) {
-        return; // Nichts zu tun
-      }
-
-      // Loading-Zustand anzeigen
-      const detailsTitle = document.getElementById('person-details-title');
-      const originalTitle = detailsTitle.innerHTML;
-      detailsTitle.innerHTML = 'Details werden geladen...';
-      
-      // AJAX-Request senden
-      const formData = new FormData();
-      formData.append('leftJson', document.getElementById('leftJson').value);
-      formData.append('rightJson', document.getElementById('rightJson').value);
-      formData.append('leftVorgangId', document.getElementById('leftVorgangId').value);
-      formData.append('leftZeitraumId', document.getElementById('leftZeitraumId').value);
-      formData.append('rightZeitraumId', document.getElementById('rightZeitraumId').value);
-      formData.append('personId', personId);
-
-      fetch('/api/person-details', {
-        method: 'POST',
-        body: formData
-      })
-      .then(response => {
-        if (!response.ok) {
-          throw new Error('Netzwerk-Antwort war nicht ok');
-        }
-        return response.json();
-      })
-      .then(data => {
-        // Erfolgreiche Antwort verarbeiten
-        updatePersonDetails(data);
-        
-        // Aktuelle Person aktualisieren
-        currentPersonId.value = personId;
-        
-        // Person-Selected-Klasse aktualisieren
-        updateSelectedPersonRow(personId);
-        
-        // Fokus beibehalten falls möglich
-        const activeElement = document.activeElement;
-        if (activeElement && activeElement.closest('.person-row')) {
-          activeElement.focus();
-        }
-      })
-      .catch(error => {
-        console.error('Fehler beim Laden der Personendetails:', error);
-        detailsTitle.innerHTML = originalTitle;
-        alert('Fehler beim Laden der Personendetails. Bitte versuchen Sie es erneut.');
-      });
-    }
-
-    function updateAfterVorgangChange(data) {
-      console.log('🔄 updateAfterVorgangChange aufgerufen mit:', data);
-      
-      // Hidden fields aktualisieren
-      document.getElementById('leftVorgangId').value = data.leftVorgangId;
-      document.getElementById('leftZeitraumId').value = data.leftZeitraumId;
-      document.getElementById('currentPersonId').value = data.personId;
-      
-      // Vorgang-Dropdown aktualisieren (falls erforderlich)
-      const vorgangSelect = document.getElementById('leftVorgangSelect');
-      if (vorgangSelect.value !== data.leftVorgangId) {
-        vorgangSelect.value = data.leftVorgangId;
-      }
-      
-      // Zeitraum-Dropdown aktualisieren
-      const zeitraumSelect = document.getElementById('leftZeitraumSelect');
-      zeitraumSelect.innerHTML = '';
-      data.leftZeitraeume.forEach(zeitraum => {
-        const option = document.createElement('option');
-        option.value = zeitraum.id;
-        option.textContent = zeitraum.beginn + ' - ' + zeitraum.ende;
-        if (zeitraum.id === data.leftZeitraumId) {
-          option.selected = true;
-        }
-        zeitraumSelect.appendChild(option);
-      });
-      
-      console.log('📋 Zeitraum-Dropdown aktualisiert mit', data.leftZeitraeume.length, 'Einträgen');
-      
-      // Haushaltsmitglieder-Tabelle aktualisieren
-      updateHaushaltsmitgliederTable(data.mitgliederRows, data.personId);
-      
-      // Personendetails aktualisieren
-      updatePersonDetails(data);
-      
-      console.log('✅ updateAfterVorgangChange abgeschlossen');
-    }
-
-    function updateAfterZeitraumChange(data) {
-      console.log('🔄 updateAfterZeitraumChange aufgerufen mit:', data);
-      
-      // Hidden field aktualisieren
-      document.getElementById('leftZeitraumId').value = data.leftZeitraumId;
-      document.getElementById('currentPersonId').value = data.personId;
-      
-      // Zeitraum-Dropdown aktualisieren (falls erforderlich)
-      const zeitraumSelect = document.getElementById('leftZeitraumSelect');
-      if (zeitraumSelect.value !== data.leftZeitraumId) {
-        zeitraumSelect.value = data.leftZeitraumId;
-      }
-      
-      // Haushaltsmitglieder-Tabelle aktualisieren
-      updateHaushaltsmitgliederTable(data.mitgliederRows, data.personId);
-      
-      // Personendetails aktualisieren
-      updatePersonDetails(data);
-      
-      console.log('✅ updateAfterZeitraumChange abgeschlossen');
-    }
-
-    function updateHaushaltsmitgliederTable(mitgliederRows, selectedPersonId) {
-      const table = document.getElementById('haushaltsmitglieder-table');
-      const tbody = table.querySelector('tbody');
-      
-      // Tbody leeren
-      tbody.innerHTML = '';
-      
-      // Neue Zeilen hinzufügen
-      mitgliederRows.forEach(row => {
-        // Hauptzeile
-        const tr = document.createElement('tr');
-        tr.className = 'person-row ' + (row.rowClass || '') + (row.personId === selectedPersonId ? ' person-selected' : '');
-        tr.setAttribute('data-person-id', row.personId);
-        tr.setAttribute('role', 'button');
-        tr.setAttribute('tabindex', '0');
-        tr.setAttribute('aria-label', 'Person auswählen: ' + (row.nameHtml || '').replace(/<[^>]*>/g, ''));
-        tr.setAttribute('onkeypress', 'handleRowKeyPress(event, this)');
-        
-        // Spalten hinzufügen
-        const expandTd = document.createElement('td');
-        expandTd.className = 'expand-col';
-        expandTd.innerHTML = `
-          <button type="button" 
-                  class="expand-btn" 
-                  data-person-id="${row.personId}"
-                  onclick="togglePersonDetailsFromButton(this)"
-                  onkeypress="handleExpandKeyPressFromButton(event, this)"
-                  aria-expanded="false"
-                  aria-controls="person-details-${row.personId}"
-                  aria-label="Details für ${(row.nameHtml || '').replace(/<[^>]*>/g, '')} anzeigen">
-            <span aria-hidden="true">▶</span>
-          </button>
-        `;
-        tr.appendChild(expandTd);
-        
-        const symbolTd = document.createElement('td');
-        symbolTd.className = 'sym ' + (row.symbolClass || '');
-        symbolTd.textContent = row.symbol || '';
-        const ariaLabel = row.symbol === '+' ? 'Hinzugefügt' : 
-                         (row.symbol === '-' ? 'Gelöscht' : 
-                         (row.symbol === '!' ? 'Geändert' : 'Unverändert'));
-        symbolTd.setAttribute('aria-label', ariaLabel);
-        tr.appendChild(symbolTd);
-        
-        // Weitere Spalten
-        const columns = ['nameHtml', 'blocknameHtml', 'geburtsdatumHtml', 'sterbedatumHtml', 
-                        'familienstandHtml', 'geschlechtHtml', 'anredeHtml', 'verwandtschaftHtml',
-                        'ausschlussgrundHtml', 'sozialstellungHtml', 'antragstellerHtml', 
-                        'zahlungsempfaengerHtml', 'aufenthaltstitelEndeHtml'];
-        
-        columns.forEach(column => {
-          const td = document.createElement('td');
-          td.innerHTML = row[column] || '';
-          tr.appendChild(td);
-        });
-        
-        tbody.appendChild(tr);
-        
-        // Detailzeile hinzufügen
-        const detailTr = document.createElement('tr');
-        detailTr.id = 'person-details-' + row.personId;
-        detailTr.className = 'person-details-row hidden';
-        detailTr.setAttribute('aria-labelledby', 'person-details-label-' + row.personId);
-        
-        const detailTd = document.createElement('td');
-        detailTd.setAttribute('colspan', '15');
-        detailTd.className = 'details-cell';
-        detailTd.innerHTML = `
-          <div class="details-container">
-            <table class="details-table">
-              <tbody>
-                <tr>
-                  <td class="detail-label">Titel:</td>
-                  <td>${row.titelHtml || ''}</td>
-                  <td class="detail-label">Geburtsort:</td>
-                  <td>${row.geburtsortHtml || ''}</td>
-                  <td class="detail-label">Telefon:</td>
-                  <td>${row.telefonHtml || ''}</td>
-                  <td class="detail-label">Aktenzeichen Soz.:</td>
-                  <td>${row.aktenzeichenSozialHtml || ''}</td>
-                </tr>
-                <tr>
-                  <td class="detail-label">Geburtsname:</td>
-                  <td>${row.geburtsnameHtml || ''}</td>
-                  <td class="detail-label">Alter Name:</td>
-                  <td>${row.alterNameHtml || ''}</td>
-                  <td class="detail-label">E-Mail:</td>
-                  <td>${row.emailHtml || ''}</td>
-                  <td class="detail-label">Postabrutionsnummer:</td>
-                  <td>${row.postabrutionsnummerHtml || ''}</td>
-                </tr>
-              </tbody>
-            </table>
-          </div>
-        `;
-        detailTr.appendChild(detailTd);
-        tbody.appendChild(detailTr);
-      });
-      
-      // Event Listeners für neue Zeilen hinzufügen
-      const newPersonRows = tbody.querySelectorAll('.person-row');
-      newPersonRows.forEach(function(row) {
-        row.addEventListener('click', function(event) {
-          // Prüfen ob das Event von einem Expand-Button kommt
-          if (event.target.classList.contains('expand-btn') || event.target.closest('.expand-btn')) {
-            return; // Button hat sein eigenes Click-Event
-          }
-          
-          const personId = this.dataset.personId;
-          if (personId) {
-            selectPersonAjax(personId);
-          }
-        });
-      });
-    }
-
-    function updatePersonDetails(data) {
-      // Titel aktualisieren
-      const detailsTitle = document.getElementById('person-details-title');
-      detailsTitle.innerHTML = 'Details zu ' + (data.personLabel && data.personLabel !== '(keine Person)' ? data.personLabel : '');
-      
-      // Einkommen-Tabelle aktualisieren
-      updateTable('einkommen-table', data.einkommenZeilen, [
-        'symbol', 'artHtml', 'artZusatzHtml', 'faktorHtml', 'betragHtml', 
-        'zuschlagHtml', 'abschlagHtml', 'werbungskostenHtml'
-      ]);
-      
-      // Pauschale Abzüge aktualisieren
-      const pauschaleZeilen = data.freibetragZeilen.filter(z => z.typ === 'P');
-      updateTable('pauschale-table', pauschaleZeilen, [
-        'symbol', 'artHtml', 'faktorHtml', 'betragHtml'
-      ]);
-      
-      // Freibeträge aktualisieren
-      const freibetraege = data.freibetragZeilen.filter(z => z.typ === 'F');
-      updateTable('freibetraege-table', freibetraege, [
-        'symbol', 'artHtml', 'faktorHtml', 'betragHtml', 'fristHtml'
-      ]);
-      
-      // Kinderbetreuung aktualisieren
-      updateTable('kinderbetreuung-table', data.kitaZeilen, [
-        'symbol', 'kindNameHtml', 'betragHtml'
-      ]);
-    }
-
-    function updateTable(tableId, data, columns) {
-      const table = document.getElementById(tableId);
-      const tbody = table.querySelector('tbody');
-      
-      // Tbody leeren
-      tbody.innerHTML = '';
-      
-      // Neue Zeilen hinzufügen
-      data.forEach(row => {
-        const tr = document.createElement('tr');
-        tr.className = row.rowClass || '';
-        
-        columns.forEach((column, index) => {
-          const td = document.createElement('td');
-          
-          if (index === 0) { // Symbol-Spalte
-            td.className = 'sym ' + (row.symbolClass || '');
-            td.textContent = row.symbol || '';
-            const ariaLabel = row.symbol === '+' ? 'Hinzugefügt' : 
-                             (row.symbol === '-' ? 'Gelöscht' : 
-                             (row.symbol === '!' ? 'Geändert' : 'Unverändert'));
-            td.setAttribute('aria-label', ariaLabel);
-          } else {
-            td.innerHTML = row[column] || '';
-          }
-          
-          tr.appendChild(td);
-        });
-        
-        tbody.appendChild(tr);
-      });
-    }
-
-    function updateSelectedPersonRow(personId) {
-      // Alle person-selected Klassen entfernen
-      const allPersonRows = document.querySelectorAll('.person-row');
-      allPersonRows.forEach(row => {
-        row.classList.remove('person-selected');
-      });
-      
-      // Neue person-selected Klasse setzen
-      const selectedRow = document.querySelector(`[data-person-id="${personId}"]`);
-      if (selectedRow) {
-        selectedRow.classList.add('person-selected');
-      }
-    }
-
-    function showLoadingState() {
-      const detailsTitle = document.getElementById('person-details-title');
-      detailsTitle.innerHTML = 'Daten werden geladen...';
-    }
-
-    function hideLoadingState() {
-      // Loading-Zustand wird durch updatePersonDetails überschrieben
-    }
-
-    // Keyboard support for toggles
-    function handleKeyPress(event, sectionId) {
-      if (event.key === 'Enter' || event.key === ' ') {
-        event.preventDefault();
-        toggleSection(sectionId);
-      }
-    }
-
-    // Keyboard support for table rows
-    function handleRowKeyPress(event, row) {
-      if (event.key === 'Enter' || event.key === ' ') {
-        // Prüfen ob das Event von einem Expand-Button kommt
-        if (event.target.classList.contains('expand-btn') || event.target.closest('.expand-btn')) {
-          return; // Lass den expand button sein eigenes Event handhaben
-        }
-        event.preventDefault();
-        row.click();
-      }
-    }
-
-    // Farbwahl-Funktionen
-    function toggleColorPicker() {
-      const popup = document.getElementById('colorPickerPopup');
-      const button = document.querySelector('.color-picker-toggle');
-      const isHidden = popup.classList.contains('hidden');
-      
-      popup.classList.toggle('hidden');
-      button.setAttribute('aria-expanded', !isHidden);
-      
-      if (!isHidden) {
-        // Focus the select when opening
-        document.getElementById('colorSchemeSelect').focus();
-      }
-    }
-
-    function updateColorNames(scheme) {
-      const colorNames = {
-        'standard': { added: 'grün', removed: 'rot', changed: 'gelb' },
-        'alternative': { added: 'blau', removed: 'pink', changed: 'orange' }
-      };
-      
-      const names = colorNames[scheme] || colorNames['standard'];
-      
-      document.getElementById('addedColorName').textContent = names.added;
-      document.getElementById('removedColorName').textContent = names.removed;
-      document.getElementById('changedColorName').textContent = names.changed;
-    }
-
-    function changeColorScheme(scheme) {
-      const html = document.documentElement;
-      
-      // Entferne alle existierenden Farbschema-Attribute
-      html.removeAttribute('data-color-scheme');
-      
-      // Setze das neue Farbschema (außer für "standard")
-      if (scheme !== 'standard') {
-        html.setAttribute('data-color-scheme', scheme);
-      }
-      
-      // Aktualisiere die Farbnamen in der Legende
-      updateColorNames(scheme);
-      
-      // Speichere die Auswahl im Cookie (365 Tage gültig)
-      setCookie('colorScheme', scheme, 365);
-      
-      // Schließe das Popup nach der Auswahl
-      const popup = document.getElementById('colorPickerPopup');
-      const button = document.querySelector('.color-picker-toggle');
-      popup.classList.add('hidden');
-      button.setAttribute('aria-expanded', 'false');
-    }
-
-    function initializeColorScheme() {
-      // Lade das gespeicherte Farbschema aus dem Cookie (Standard: "standard" = Ihre ursprünglichen Farben)
-      const savedScheme = getCookie('colorScheme') || 'standard';
-      
-      // Setze das Farbschema
-      const html = document.documentElement;
-      if (savedScheme !== 'standard') {
-        html.setAttribute('data-color-scheme', savedScheme);
-      }
-      
-      // Aktualisiere die Farbnamen
-      updateColorNames(savedScheme);
-      
-      // Setze den Wert in der Dropdown-Box
-      const select = document.getElementById('colorSchemeSelect');
-      if (select) {
-        select.value = savedScheme;
-      }
-    }
-
-    // Escape key support for popup
-    document.addEventListener('keydown', function(event) {
-      if (event.key === 'Escape') {
-        const popup = document.getElementById('colorPickerPopup');
-        const button = document.querySelector('.color-picker-toggle');
-        if (!popup.classList.contains('hidden')) {
-          popup.classList.add('hidden');
-          button.setAttribute('aria-expanded', 'false');
-          button.focus();
-        }
-      }
-    });
-
-    // Schließe das Popup wenn außerhalb geklickt wird
-    document.addEventListener('click', function(event) {
-      const container = document.querySelector('.color-picker-container');
-      const popup = document.getElementById('colorPickerPopup');
-      const button = document.querySelector('.color-picker-toggle');
-      
-      if (container && !container.contains(event.target)) {
-        popup.classList.add('hidden');
-        button.setAttribute('aria-expanded', 'false');
-      }
-    });
-
-    // Toggle-Funktion für ausklappbare Sektionen
-    function toggleSection(sectionId) {
-      const content = document.getElementById(sectionId + '-content');
-      const icon = document.getElementById(sectionId + '-icon');
-      const header = content.parentElement.querySelector('.section-header');
-      
-      if (content.classList.contains('collapsed')) {
-        content.classList.remove('collapsed');
-        icon.classList.remove('collapsed');
-        icon.textContent = '▼';
-        header.setAttribute('aria-expanded', 'true');
-      } else {
-        content.classList.add('collapsed');
-        icon.classList.add('collapsed');
-        icon.textContent = '▶';
-        header.setAttribute('aria-expanded', 'false');
-      }
-    }
-
-    document.addEventListener('DOMContentLoaded', function() {
-      // Initialisiere das Farbschema
-      initializeColorScheme();
-      
-      // Add click event listeners to all person rows - jetzt mit AJAX
-      const personRows = document.querySelectorAll('.person-row');
-      personRows.forEach(function(row) {
-        row.addEventListener('click', function(event) {
-          // Prüfen ob das Event von einem Expand-Button kommt
-          if (event.target.classList.contains('expand-btn') || event.target.closest('.expand-btn')) {
-            return; // Button hat sein eigenes Click-Event
-          }
-          
-          const personId = this.dataset.personId;
-          if (personId) {
-            selectPersonAjax(personId);
-          }
-        });
-      });
-      
-      // Debug-Info ausgeben
-      console.log('🔧 DOM geladen - Debug Info:');
-      console.log('  leftVorgangSelect:', document.getElementById('leftVorgangSelect'));
-      console.log('  leftZeitraumSelect:', document.getElementById('leftZeitraumSelect'));
-      console.log('  leftVorgangId (hidden):', document.getElementById('leftVorgangId'));
-      console.log('  leftZeitraumId (hidden):', document.getElementById('leftZeitraumId'));
-    });
-  </script>
 </body>
 </html>