vergleich.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. <!DOCTYPE html>
  2. <html lang="de" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  6. <title>DIWO Vergleich – Haushaltsmitglieder/Einkommen</title>
  7. <link rel="stylesheet" href="/styles.css"/>
  8. </head>
  9. <body>
  10. <header class="diwo-header" role="banner">
  11. <div class="header-row-1">
  12. <div class="kunde-logo">
  13. <div th:if="${activeKunde != null and !#strings.isEmpty(activeKunde)}" class="kunde-logo-container">
  14. <img th:src="@{'/images/' + ${#strings.toLowerCase(activeKunde)} + '.jpg'}"
  15. th:alt="${activeKunde != null and !#strings.isEmpty(activeKunde) ? activeKunde + ' Logo' : 'Kunden-Logo'}"
  16. class="kunde-logo-image"
  17. th:onerror="'this.style.display=\\'none\\''"/>
  18. </div>
  19. </div>
  20. <div class="diwo-title">
  21. <h1>DIWO</h1>
  22. <h2>Vergleich</h2>
  23. </div>
  24. <div class="page-title" role="heading" aria-level="2">Haushaltsmitglieder/Einkommen</div>
  25. </div>
  26. <div class="header-row-2">
  27. <div class="aktenzeichen" th:if="${stammakteRight != null and !#strings.isEmpty(stammakteRight.aktenzeichen)}" role="contentinfo" aria-label="Aktenzeichen">
  28. <span th:text="${stammakteRight.aktenzeichen}">01/01/119979</span>
  29. </div>
  30. <div class="aktenzeichen" th:if="${(stammakteRight == null or #strings.isEmpty(stammakteRight.aktenzeichen)) and stammvorgangRight != null}" role="contentinfo" aria-label="Aktenzeichen">
  31. <span th:text="${stammvorgangRight.aktenzeichen}">01/01/119979</span>
  32. </div>
  33. <div class="antragsteller-info" th:if="${stammakteRight != null}" role="contentinfo" aria-label="Antragsteller-Informationen">
  34. <span th:if="${!#strings.isEmpty(stammakteRight.antragstellername)}" th:text="${stammakteRight.antragstellername}">Müller, Tobias</span>
  35. <span th:if="${!#strings.isEmpty(stammakteRight.antragstellerGeburtsdatum)}"> - <span th:text="${stammakteRight.antragstellerGeburtsdatum}">01.01.2000</span></span>
  36. </div>
  37. <div class="location-info" th:if="${stammakteRight != null and !#strings.isEmpty(stammakteRight.anschrift)}" role="contentinfo" aria-label="Anschrift">
  38. <span th:text="${stammakteRight.anschrift}">Unter den Linden 1, 10117 Berlin</span>
  39. </div>
  40. </div>
  41. <div class="header-row-3">
  42. <div class="vorgang-info" th:if="${stammvorgangRight != null}" role="contentinfo" aria-label="Vorgang in Bearbeitung">
  43. <span>Vorgang in der Bearbeitung: </span>
  44. <span th:if="${!#strings.isEmpty(stammvorgangRight.bemerkung)}" th:text="${stammvorgangRight.bemerkung}">Weiterleistungsantrag</span>
  45. <span th:if="${#strings.isEmpty(stammvorgangRight.bemerkung)}">-</span>
  46. <span th:if="${!#strings.isEmpty(stammvorgangRight.beginn) and !#strings.isEmpty(stammvorgangRight.ende)}">
  47. (<span th:text="${stammvorgangRight.beginn}">01.01.2026</span> - <span th:text="${stammvorgangRight.ende}">31.12.2027</span>)
  48. </span>
  49. </div>
  50. <div class="zeitraum-controls">
  51. <form method="post" action="/vergleich" class="zeitraum-form" role="form" aria-label="Zeitraum wechseln">
  52. <input type="hidden" name="schemaName" th:value="${schemaName}"/>
  53. <input type="hidden" name="validate" th:value="${validate}"/>
  54. <input type="hidden" name="leftJson" th:value="${leftJson}"/>
  55. <input type="hidden" name="rightJson" th:value="${rightJson}"/>
  56. <input type="hidden" name="leftVorgangId" th:value="${leftVorgangId}"/>
  57. <input type="hidden" name="leftZeitraumId" th:value="${leftZeitraumId}"/>
  58. <input type="hidden" name="kunde" th:value="${activeKunde}"/>
  59. <label for="rightZeitraumId">Zeitraum:</label>
  60. <select name="rightZeitraumId" id="rightZeitraumId" class="zeitraum-select" aria-describedby="zeitraum-hint">
  61. <option th:each="z : ${zeitraeumeRight}" th:value="${z.id}"
  62. th:selected="${z.id == rightZeitraumId}"
  63. th:text="${z.beginn} + ' - ' + ${z.ende}"></option>
  64. </select>
  65. <button type="submit" class="wechseln-btn" aria-label="Zeitraum wechseln">▼</button>
  66. </form>
  67. </div>
  68. </div>
  69. </header>
  70. <!-- Vergleichslegende -->
  71. <section class="vergleich-legende" role="complementary" aria-labelledby="legende-title">
  72. <div class="legende-title" id="legende-title">
  73. <strong>Legende:</strong>
  74. </div>
  75. <div class="legende-item">
  76. <span class="sym plus" aria-label="Hinzugefügt">+</span>
  77. <span>hinzugefügt (<span id="addedColorName">grün</span>)</span>
  78. </div>
  79. <div class="legende-spacer" aria-hidden="true">|</div>
  80. <div class="legende-item">
  81. <span class="sym minus" aria-label="Gelöscht">−</span>
  82. <span>gelöscht (<span id="removedColorName">rot</span>)</span>
  83. </div>
  84. <div class="legende-spacer" aria-hidden="true">|</div>
  85. <div class="legende-item">
  86. <span class="sym warn" aria-label="Geändert">!</span>
  87. <span>geändert (<span id="changedColorName">gelb</span>)</span>
  88. </div>
  89. <div class="legende-spacer" aria-hidden="true">|</div>
  90. <div class="legende-item">
  91. <span class="selected-indicator-text">ausgewählte Person (blauer Rahmen)</span>
  92. </div>
  93. <!-- Farbwahl -->
  94. <div class="color-picker-container">
  95. <button class="color-picker-toggle" onclick="toggleColorPicker()" aria-label="Farbschema-Einstellungen öffnen" aria-expanded="false" aria-controls="colorPickerPopup">⚙️</button>
  96. <div class="color-picker-popup hidden" id="colorPickerPopup" role="dialog" aria-labelledby="color-picker-label">
  97. <label id="color-picker-label" for="colorSchemeSelect">Farbwahl:</label>
  98. <select id="colorSchemeSelect" class="color-scheme-select" onchange="changeColorScheme(this.value)" aria-describedby="color-scheme-hint">
  99. <option value="standard">Standard</option>
  100. <option value="alternative">Pastell</option>
  101. </select>
  102. </div>
  103. </div>
  104. </section>
  105. <!-- Haushaltsmitglieder - klickbare Zeilen -->
  106. <main role="main">
  107. <div class="section-container">
  108. <div class="section-header" onclick="toggleSection('haushaltsmitglieder')" role="button" tabindex="0"
  109. onkeypress="handleKeyPress(event, 'haushaltsmitglieder')" aria-expanded="true" aria-controls="haushaltsmitglieder-content">
  110. <span class="toggle-icon" id="haushaltsmitglieder-icon" aria-hidden="true">▼</span>
  111. <h3>Haushaltsmitglieder</h3>
  112. </div>
  113. <div class="section-content" id="haushaltsmitglieder-content">
  114. <table role="table" aria-labelledby="haushaltsmitglieder-caption">
  115. <thead>
  116. <tr>
  117. <th scope="col">Änderung</th>
  118. <th scope="col">Name</th>
  119. <th scope="col">Geburtsdatum</th>
  120. <th scope="col">Familienstand</th>
  121. <th scope="col">Geschlecht</th>
  122. <th scope="col">Verwandtschaft</th>
  123. <th scope="col">Sozialstellung</th>
  124. <th scope="col">Kind</th>
  125. </tr>
  126. </thead>
  127. <tbody>
  128. <tr th:each="r : ${mitgliederRows}"
  129. th:class="'person-row ' + ${r.rowClass} + ((${r.personId} == ${personId}) ? ' person-selected' : '')"
  130. th:data-person-id="${r.personId}"
  131. role="button"
  132. tabindex="0"
  133. th:aria-label="'Person auswählen: ' + ${r.nameHtml}"
  134. onkeypress="handleRowKeyPress(event, this)">
  135. <td class="sym" th:classappend="' ' + ${r.symbolClass}" th:text="${r.symbol}"
  136. th:aria-label="${r.symbol == '+' ? 'Hinzugefügt' : (r.symbol == '-' ? 'Gelöscht' : (r.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
  137. <td th:utext="${r.nameHtml}"></td>
  138. <td th:utext="${r.geburtsdatumHtml}"></td>
  139. <td th:utext="${r.familienstandHtml}"></td>
  140. <td th:utext="${r.geschlechtHtml}"></td>
  141. <td th:utext="${r.verwandtschaftHtml}"></td>
  142. <td th:utext="${r.sozialstellungHtml}"></td>
  143. <td th:utext="${r.istKindHtml}"></td>
  144. </tr>
  145. </tbody>
  146. </table>
  147. </div>
  148. </div>
  149. <!-- Hidden form for person selection -->
  150. <form id="personForm" method="post" action="/vergleich" style="display: none;" aria-hidden="true">
  151. <input type="hidden" name="leftJson" th:value="${leftJson}"/>
  152. <input type="hidden" name="rightJson" th:value="${rightJson}"/>
  153. <input type="hidden" name="validate" th:value="${validate}"/>
  154. <input type="hidden" name="schemaName" th:value="${schemaName}"/>
  155. <input type="hidden" name="leftVorgangId" th:value="${leftVorgangId}"/>
  156. <input type="hidden" name="leftZeitraumId" th:value="${leftZeitraumId}"/>
  157. <input type="hidden" name="rightZeitraumId" th:value="${rightZeitraumId}"/>
  158. <input type="hidden" name="kunde" th:value="${activeKunde}"/>
  159. <input type="hidden" name="personId" id="selectedPersonId"/>
  160. </form>
  161. <h2>Details zu <span th:if="${personLabel != null and !#strings.isEmpty(personLabel) and personLabel != '(keine Person)'}"><span th:text="${personLabel}"> - </span></span></h2>
  162. <!-- Einkommen -->
  163. <div class="section-container">
  164. <div class="section-header" onclick="toggleSection('einkommen')" role="button" tabindex="0"
  165. onkeypress="handleKeyPress(event, 'einkommen')" aria-expanded="true" aria-controls="einkommen-content">
  166. <span class="toggle-icon" id="einkommen-icon" aria-hidden="true">▼</span>
  167. <h3>Einkommen</h3>
  168. </div>
  169. <div class="section-content" id="einkommen-content">
  170. <table role="table" aria-labelledby="einkommen-caption">
  171. <thead>
  172. <tr>
  173. <th scope="col">Änderung</th>
  174. <th scope="col">Art</th>
  175. <th scope="col">Frequenz</th>
  176. <th scope="col">Betrag</th>
  177. <th scope="col">Zuschlag</th>
  178. <th scope="col">Abschlag</th>
  179. <th scope="col">Werbungskosten</th>
  180. <th scope="col">Berechnungsart</th>
  181. </tr>
  182. </thead>
  183. <tbody>
  184. <tr th:each="z : ${einkommenZeilen}" th:class="${z.rowClass}">
  185. <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
  186. th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
  187. <td th:utext="${z.artHtml}"></td>
  188. <td th:utext="${z.frequenzHtml}"></td>
  189. <td th:utext="${z.betragHtml}"></td>
  190. <td th:utext="${z.zuschlagHtml}"></td>
  191. <td th:utext="${z.abschlagHtml}"></td>
  192. <td th:utext="${z.werbungskostenHtml}"></td>
  193. <td th:utext="${z.berechnungsartHtml}"></td>
  194. </tr>
  195. </tbody>
  196. </table>
  197. </div>
  198. </div>
  199. <div class="freibetrag-container">
  200. <div class="freibetrag-section">
  201. <div class="section-container">
  202. <div class="section-header" onclick="toggleSection('pauschale')" role="button" tabindex="0"
  203. onkeypress="handleKeyPress(event, 'pauschale')" aria-expanded="true" aria-controls="pauschale-content">
  204. <span class="toggle-icon" id="pauschale-icon" aria-hidden="true">▼</span>
  205. <h3>Pauschale Abzüge</h3>
  206. </div>
  207. <div class="section-content" id="pauschale-content">
  208. <table role="table" aria-labelledby="pauschale-caption">
  209. <thead>
  210. <tr>
  211. <th scope="col">Änderung</th>
  212. <th scope="col">Art</th>
  213. <th scope="col">Frequenz</th>
  214. <th scope="col">Betrag</th>
  215. </tr>
  216. </thead>
  217. <tbody>
  218. <tr th:each="z : ${freibetragZeilen}" th:if="${z.typ == 'P'}" th:class="${z.rowClass}">
  219. <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
  220. th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
  221. <td th:utext="${z.artHtml}"></td>
  222. <td th:utext="${z.frequenzHtml}"></td>
  223. <td th:utext="${z.betragHtml}"></td>
  224. </tr>
  225. </tbody>
  226. </table>
  227. </div>
  228. </div>
  229. </div>
  230. <div class="freibetrag-section">
  231. <div class="section-container">
  232. <div class="section-header" onclick="toggleSection('freibetraege')" role="button" tabindex="0"
  233. onkeypress="handleKeyPress(event, 'freibetraege')" aria-expanded="true" aria-controls="freibetraege-content">
  234. <span class="toggle-icon" id="freibetraege-icon" aria-hidden="true">▼</span>
  235. <h3>Freibeträge</h3>
  236. </div>
  237. <div class="section-content" id="freibetraege-content">
  238. <table role="table" aria-labelledby="freibetraege-caption">
  239. <thead>
  240. <tr>
  241. <th scope="col">Änderung</th>
  242. <th scope="col">Art</th>
  243. <th scope="col">Frequenz</th>
  244. <th scope="col">Betrag</th>
  245. <th scope="col">Frist</th>
  246. </tr>
  247. </thead>
  248. <tbody>
  249. <tr th:each="z : ${freibetragZeilen}" th:if="${z.typ == 'F'}" th:class="${z.rowClass}">
  250. <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
  251. th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
  252. <td th:utext="${z.artHtml}"></td>
  253. <td th:utext="${z.frequenzHtml}"></td>
  254. <td th:utext="${z.betragHtml}"></td>
  255. <td th:utext="${z.fristHtml}"></td>
  256. </tr>
  257. </tbody>
  258. </table>
  259. </div>
  260. </div>
  261. </div>
  262. </div>
  263. <!-- Kinderbetreuungskosten -->
  264. <div class="kinderbetreuung-container">
  265. <div class="section-container">
  266. <div class="section-header" onclick="toggleSection('kinderbetreuung')" role="button" tabindex="0"
  267. onkeypress="handleKeyPress(event, 'kinderbetreuung')" aria-expanded="true" aria-controls="kinderbetreuung-content">
  268. <span class="toggle-icon" id="kinderbetreuung-icon" aria-hidden="true">▼</span>
  269. <h3>Kinderbetreuungskosten</h3>
  270. </div>
  271. <div class="section-content" id="kinderbetreuung-content">
  272. <table role="table" aria-labelledby="kinderbetreuung-caption">
  273. <thead>
  274. <tr>
  275. <th scope="col">Änderung</th>
  276. <th scope="col">Kind</th>
  277. <th scope="col">Betrag</th>
  278. </tr>
  279. </thead>
  280. <tbody>
  281. <tr th:each="z : ${kitaZeilen}" th:class="${z.rowClass}">
  282. <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
  283. th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
  284. <td th:utext="${z.kindNameHtml}"></td>
  285. <td th:utext="${z.betragHtml}"></td>
  286. </tr>
  287. </tbody>
  288. </table>
  289. </div>
  290. </div>
  291. </div>
  292. </main>
  293. <script>
  294. // Cookie Verwaltung
  295. function setCookie(name, value, days) {
  296. const expires = new Date();
  297. expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
  298. document.cookie = name + '=' + value + ';expires=' + expires.toUTCString() + ';path=/';
  299. }
  300. function getCookie(name) {
  301. const nameEQ = name + '=';
  302. const ca = document.cookie.split(';');
  303. for(let i = 0; i < ca.length; i++) {
  304. let c = ca[i];
  305. while (c.charAt(0) === ' ') c = c.substring(1, c.length);
  306. if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
  307. }
  308. return null;
  309. }
  310. // Keyboard support for toggles
  311. function handleKeyPress(event, sectionId) {
  312. if (event.key === 'Enter' || event.key === ' ') {
  313. event.preventDefault();
  314. toggleSection(sectionId);
  315. }
  316. }
  317. // Keyboard support for table rows
  318. function handleRowKeyPress(event, row) {
  319. if (event.key === 'Enter' || event.key === ' ') {
  320. event.preventDefault();
  321. row.click();
  322. }
  323. }
  324. // Farbwahl-Funktionen
  325. function toggleColorPicker() {
  326. const popup = document.getElementById('colorPickerPopup');
  327. const button = document.querySelector('.color-picker-toggle');
  328. const isHidden = popup.classList.contains('hidden');
  329. popup.classList.toggle('hidden');
  330. button.setAttribute('aria-expanded', !isHidden);
  331. if (!isHidden) {
  332. // Focus the select when opening
  333. document.getElementById('colorSchemeSelect').focus();
  334. }
  335. }
  336. function updateColorNames(scheme) {
  337. const colorNames = {
  338. 'standard': { added: 'grün', removed: 'rot', changed: 'gelb' },
  339. 'alternative': { added: 'blau', removed: 'pink', changed: 'orange' }
  340. };
  341. const names = colorNames[scheme] || colorNames['standard'];
  342. document.getElementById('addedColorName').textContent = names.added;
  343. document.getElementById('removedColorName').textContent = names.removed;
  344. document.getElementById('changedColorName').textContent = names.changed;
  345. }
  346. function changeColorScheme(scheme) {
  347. const html = document.documentElement;
  348. // Entferne alle existierenden Farbschema-Attribute
  349. html.removeAttribute('data-color-scheme');
  350. // Setze das neue Farbschema (außer für "standard")
  351. if (scheme !== 'standard') {
  352. html.setAttribute('data-color-scheme', scheme);
  353. }
  354. // Aktualisiere die Farbnamen in der Legende
  355. updateColorNames(scheme);
  356. // Speichere die Auswahl im Cookie (365 Tage gültig)
  357. setCookie('colorScheme', scheme, 365);
  358. // Schließe das Popup nach der Auswahl
  359. const popup = document.getElementById('colorPickerPopup');
  360. const button = document.querySelector('.color-picker-toggle');
  361. popup.classList.add('hidden');
  362. button.setAttribute('aria-expanded', 'false');
  363. }
  364. function initializeColorScheme() {
  365. // Lade das gespeicherte Farbschema aus dem Cookie (Standard: "standard" = Ihre ursprünglichen Farben)
  366. const savedScheme = getCookie('colorScheme') || 'standard';
  367. // Setze das Farbschema
  368. const html = document.documentElement;
  369. if (savedScheme !== 'standard') {
  370. html.setAttribute('data-color-scheme', savedScheme);
  371. }
  372. // Aktualisiere die Farbnamen
  373. updateColorNames(savedScheme);
  374. // Setze den Wert in der Dropdown-Box
  375. const select = document.getElementById('colorSchemeSelect');
  376. if (select) {
  377. select.value = savedScheme;
  378. }
  379. }
  380. // Escape key support for popup
  381. document.addEventListener('keydown', function(event) {
  382. if (event.key === 'Escape') {
  383. const popup = document.getElementById('colorPickerPopup');
  384. const button = document.querySelector('.color-picker-toggle');
  385. if (!popup.classList.contains('hidden')) {
  386. popup.classList.add('hidden');
  387. button.setAttribute('aria-expanded', 'false');
  388. button.focus();
  389. }
  390. }
  391. });
  392. // Schließe das Popup wenn außerhalb geklickt wird
  393. document.addEventListener('click', function(event) {
  394. const container = document.querySelector('.color-picker-container');
  395. const popup = document.getElementById('colorPickerPopup');
  396. const button = document.querySelector('.color-picker-toggle');
  397. if (container && !container.contains(event.target)) {
  398. popup.classList.add('hidden');
  399. button.setAttribute('aria-expanded', 'false');
  400. }
  401. });
  402. // Toggle-Funktion für ausklappbare Sektionen
  403. function toggleSection(sectionId) {
  404. const content = document.getElementById(sectionId + '-content');
  405. const icon = document.getElementById(sectionId + '-icon');
  406. const header = content.parentElement.querySelector('.section-header');
  407. if (content.classList.contains('collapsed')) {
  408. content.classList.remove('collapsed');
  409. icon.classList.remove('collapsed');
  410. icon.textContent = '▼';
  411. header.setAttribute('aria-expanded', 'true');
  412. } else {
  413. content.classList.add('collapsed');
  414. icon.classList.add('collapsed');
  415. icon.textContent = '▶';
  416. header.setAttribute('aria-expanded', 'false');
  417. }
  418. }
  419. document.addEventListener('DOMContentLoaded', function() {
  420. // Initialisiere das Farbschema
  421. initializeColorScheme();
  422. // Add click event listeners to all person rows
  423. const personRows = document.querySelectorAll('.person-row');
  424. personRows.forEach(function(row) {
  425. row.addEventListener('click', function() {
  426. const personId = this.dataset.personId;
  427. if (personId) {
  428. document.getElementById('selectedPersonId').value = personId;
  429. document.getElementById('personForm').submit();
  430. }
  431. });
  432. });
  433. });
  434. </script>
  435. </body>
  436. </html>