Quellcode durchsuchen

Font Awesome eingebaut
Copy für alte werte eingebaut
Farbauswahl jetzt am ende der Legende

Leonard Schulz vor 1 Jahr
Ursprung
Commit
384298d837

+ 3 - 3
src/main/java/com/tsystems/inwo/vgl/service/DataComparisonService.java

@@ -105,9 +105,9 @@ public class DataComparisonService {
 
     public String getSymbolFor(String status) {
         return switch (status) {
-            case "ADDED" -> "+";
-            case "REMOVED" -> "−";
-            case "CHANGED" -> "!";
+            case "ADDED" -> "<i class='fas fa-plus'></i>";
+            case "REMOVED" -> "<i class='fas fa-minus'></i>";
+            case "CHANGED" -> "<i class='fas fa-exclamation'></i>";
             default -> "";
         };
     }

+ 21 - 2
src/main/java/com/tsystems/inwo/vgl/service/HtmlRenderingService.java

@@ -74,15 +74,34 @@ public class HtmlRenderingService {
         } else if (isDeletion) {
             // Field was deleted (alt had value, now is empty)
             return "<span class='cell-deleted'>leer" + 
-                   "<div class='old'>gelöscht (war: " + escape(oldVal) + ")</div></span>";
+                   "<div class='old'>gelöscht (war: " + escape(oldVal) + ")" +
+                   buildCopyButton(oldVal) + "</div></span>";
         } else {
             // Field was changed (both old and new have values)
             String displayOldVal = (oldVal == null || oldVal.trim().isEmpty()) ? "leer" : oldVal;
             return "<span class='cell-changed'>" + escape(newVal) + 
-                   "<div class='old'>geändert (war: " + escape(displayOldVal) + ")</div></span>";
+                   "<div class='old'>geändert (war: " + escape(displayOldVal) + ")" +
+                   buildCopyButton(oldVal) + "</div></span>";
         }
     }
     
+    /**
+     * Builds a copy button for copying the old value to clipboard
+     */
+    private String buildCopyButton(String oldValue) {
+        if (oldValue == null || oldValue.trim().isEmpty()) {
+            return "";
+        }
+        
+        String escapedValue = escape(oldValue);
+        return " <button type='button' class='copy-btn' " +
+               "title='Alten Wert in Zwischenablage kopieren' " +
+               "data-copy-value='" + escapedValue + "' " +
+               "onclick='copyToClipboard(this)' " +
+               "onkeypress='handleCopyKeyPress(event, this)'>" +
+               "<i class='fas fa-copy'></i></button>";
+    }
+    
     public String escape(String s) {
         if (s == null) return "";
         return s.replace("&", "&")

+ 59 - 0
src/main/resources/static/css/global.css

@@ -274,6 +274,65 @@ td:has(.cell-deleted) {
   font-style: italic;
 }
 
+/* =============================================================================
+   KOPIER-BUTTONS FÜR ALTE WERTE
+   ============================================================================= */
+.copy-btn {
+  display: inline;
+  margin-left: 0.25rem;
+  padding: 0;
+  font-size: 10px;
+  line-height: 1;
+  background: transparent;
+  border: none;
+  cursor: pointer;
+  color: var(--schwarz);
+  transition: all 0.15s ease;
+  vertical-align: baseline;
+  opacity: 0.6;
+  height: auto;
+  width: auto;
+  min-height: unset !important;
+}
+
+.copy-btn i {
+  font-size: 10px;
+  line-height: 1;
+  vertical-align: baseline;
+}
+
+.copy-btn:hover {
+  opacity: 1;
+  transform: scale(1.1);
+  color: var(--schwarz);
+}
+
+.copy-btn:focus {
+  outline: 1px solid var(--focus-color);
+  outline-offset: 1px;
+  opacity: 1;
+}
+
+.copy-btn:active {
+  transform: scale(0.95);
+}
+
+/* Feedback-Stile für Kopier-Buttons */
+.copy-btn.copy-success {
+  color: var(--gruen);
+  opacity: 1;
+}
+
+.copy-btn.copy-error {
+  color: var(--rot);
+  opacity: 1;
+}
+
+/* Sicherstellen, dass Kopier-Buttons in der old-Zeile richtig angezeigt werden */
+.old .copy-btn {
+  font-style: normal;
+}
+
 /* =============================================================================
    SECTION COMPONENTS (COLLAPSIBLE SECTIONS)
    ============================================================================= */

+ 0 - 1
src/main/resources/static/css/legend.css

@@ -80,7 +80,6 @@
   display: flex;
   align-items: center;
   gap: 4px;
-  margin-left: auto;
 }
 
 .color-picker-toggle {

+ 0 - 1
src/main/resources/static/css/miete.css

@@ -104,7 +104,6 @@
 
 .miete-grid-table .section-header-cell {
   font-weight: bold;
-  color: var(--diwo-blau);
   background: var(--grau-stark);
   text-align: left;
   border-bottom: none;

+ 131 - 0
src/main/resources/static/js/clipboard-utils.js

@@ -0,0 +1,131 @@
+/**
+ * DIWO Vergleich - Clipboard Utilities
+ * 
+ * Funktionen zum Kopieren von Werten in die Zwischenablage
+ */
+
+/**
+ * Kopiert einen Wert in die Zwischenablage
+ * @param {HTMLElement} button - Der Kopier-Button
+ */
+function copyToClipboard(button) {
+    const value = button.getAttribute('data-copy-value');
+    
+    if (!value) {
+        console.warn('Kein Wert zum Kopieren gefunden');
+        return;
+    }
+    
+    // Verwende die moderne Clipboard API falls verfügbar
+    if (navigator.clipboard && window.isSecureContext) {
+        navigator.clipboard.writeText(value).then(() => {
+            showCopyFeedback(button, true);
+        }).catch(err => {
+            console.error('Fehler beim Kopieren in die Zwischenablage:', err);
+            fallbackCopyToClipboard(value, button);
+        });
+    } else {
+        // Fallback für ältere Browser
+        fallbackCopyToClipboard(value, button);
+    }
+}
+
+/**
+ * Fallback-Methode zum Kopieren in die Zwischenablage
+ * @param {string} text - Der zu kopierende Text
+ * @param {HTMLElement} button - Der Kopier-Button
+ */
+function fallbackCopyToClipboard(text, button) {
+    // Erstelle temporäres Textarea-Element
+    const textArea = document.createElement('textarea');
+    textArea.value = text;
+    
+    // Setze das Element außerhalb des sichtbaren Bereichs
+    textArea.style.position = 'fixed';
+    textArea.style.left = '-999999px';
+    textArea.style.top = '-999999px';
+    
+    document.body.appendChild(textArea);
+    textArea.focus();
+    textArea.select();
+    
+    try {
+        const successful = document.execCommand('copy');
+        showCopyFeedback(button, successful);
+        
+        if (!successful) {
+            console.error('Fallback-Kopieren fehlgeschlagen');
+        }
+    } catch (err) {
+        console.error('Fehler beim Fallback-Kopieren:', err);
+        showCopyFeedback(button, false);
+    } finally {
+        document.body.removeChild(textArea);
+    }
+}
+
+/**
+ * Zeigt visuelles Feedback nach dem Kopieren
+ * @param {HTMLElement} button - Der Kopier-Button
+ * @param {boolean} success - Ob das Kopieren erfolgreich war
+ */
+function showCopyFeedback(button, success) {
+    if (success) {
+        button.innerHTML = '<i class="fas fa-check"></i>';
+        button.title = 'Kopiert!';
+        button.classList.add('copy-success');
+        // Haken bleibt permanent - kein Timeout
+    } else {
+        button.innerHTML = '<i class="fas fa-times"></i>';
+        button.title = 'Kopieren fehlgeschlagen';
+        button.classList.add('copy-error');
+        
+        // Nur bei Fehler nach 3 Sekunden zurücksetzen
+        setTimeout(() => {
+            button.innerHTML = '<i class="fas fa-copy"></i>';
+            button.title = 'Alten Wert in Zwischenablage kopieren';
+            button.classList.remove('copy-error');
+        }, 3000);
+    }
+}
+
+/**
+ * Behandelt Tastendruck-Ereignisse für Kopier-Buttons
+ * @param {KeyboardEvent} event - Das Tastatur-Event
+ * @param {HTMLElement} button - Der Kopier-Button
+ */
+function handleCopyKeyPress(event, button) {
+    // Enter oder Leertaste
+    if (event.key === 'Enter' || event.key === ' ') {
+        event.preventDefault();
+        copyToClipboard(button);
+    }
+}
+
+/**
+ * Initialisiert alle Kopier-Buttons auf der Seite
+ */
+function initializeCopyButtons() {
+    const copyButtons = document.querySelectorAll('.copy-btn');
+    
+    copyButtons.forEach(button => {
+        // Stelle sicher, dass der Button focusierbar ist
+        if (!button.hasAttribute('tabindex')) {
+            button.setAttribute('tabindex', '0');
+        }
+        
+        // Füge Accessibility-Attribute hinzu
+        button.setAttribute('role', 'button');
+        button.setAttribute('aria-label', 'Alten Wert in Zwischenablage kopieren');
+    });
+}
+
+// Initialisiere Kopier-Buttons wenn das DOM geladen ist
+document.addEventListener('DOMContentLoaded', initializeCopyButtons);
+
+// Initialisiere auch bei dynamischen Inhalten (falls AJAX verwendet wird)
+document.addEventListener('DOMNodeInserted', function(event) {
+    if (event.target.classList && event.target.classList.contains('copy-btn')) {
+        initializeCopyButtons();
+    }
+});

+ 7 - 6
src/main/resources/templates/fragments/legend.html

@@ -4,27 +4,28 @@
       <strong>Legende:</strong>
     </div>
     <div class="legende-item">
-      <span class="sym plus" aria-label="Hinzugefügt">+</span> 
+      <span class="sym plus" aria-label="Hinzugefügt"><i class="fas fa-plus"></i></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 class="sym minus" aria-label="Gelöscht"><i class="fas fa-minus"></i></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 class="sym warn" aria-label="Geändert"><i class="fas fa-exclamation"></i></span> 
       <span>geändert (<span id="changedColorName">gelb</span>)</span>
     </div>
-    <div class="legende-spacer" aria-hidden="true">|</div>
-    <div class="legende-item hhm-only" style="display: none;">
+    <div class="legende-spacer hhm-only" aria-hidden="true">|</div>
+    <div class="legende-item hhm-only">
       <span class="selected-indicator-text">ausgewählte Person (blauer Rahmen)</span>
     </div>
+    <div class="legende-spacer" aria-hidden="true">|</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>
+      <button class="color-picker-toggle" onclick="toggleColorPicker()" aria-label="Farbschema-Einstellungen öffnen" aria-expanded="false" aria-controls="colorPickerPopup"><i class="fas fa-cog"></i></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">

+ 12 - 10
src/main/resources/templates/hhm-einkommen.html

@@ -8,12 +8,14 @@
     <link rel="stylesheet" href="/css/header.css"/>
     <link rel="stylesheet" href="/css/legend.css"/>
     <link rel="stylesheet" href="/css/hhm-einkommen.css"/>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"/>
     <!-- JavaScript Module -->
     <script src="/js/cookie-utils.js"></script>
     <script src="/js/ajax-handlers.js"></script>
     <script src="/js/ui-updater-hhm-einkommen.js"></script>
     <script src="/js/interactions.js"></script>
     <script src="/js/color-scheme.js"></script>
+    <script src="/js/clipboard-utils.js"></script>
     <script src="/js/app.js"></script>
 </head>
 <body class="hhm-page">
@@ -74,8 +76,8 @@
                     <span aria-hidden="true">▶</span>
                   </button>
                 </td>
-                <td class="sym" th:classappend="' ' + ${r.symbolClass}" th:text="${r.symbol}" 
-                    th:aria-label="${r.symbol == '+' ? 'Hinzugefügt' : (r.symbol == '-' ? 'Gelöscht' : (r.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+                <td th:class="'sym ' + ${r.symbolClass}" th:utext="${r.symbol}" 
+                    th:aria-label="${r.symbol.contains('fa-plus') ? 'Hinzugefügt' : (r.symbol.contains('fa-minus') ? 'Gelöscht' : (r.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
                 <td th:utext="${r.nameHtml}"></td>
                 <td th:utext="${r.blocknameHtml}"></td>
                 <td th:utext="${r.geburtsdatumHtml}"></td>
@@ -165,8 +167,8 @@
           </thead>
           <tbody>
             <tr th:each="z : ${einkommenZeilen}" th:class="${z.rowClass}">
-              <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
-                  th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+              <td th:class="'sym ' + ${z.symbolClass}" th:utext="${z.symbol}"
+                  th:aria-label="${z.symbol.contains('fa-plus') ? 'Hinzugefügt' : (z.symbol.contains('fa-minus') ? 'Gelöscht' : (z.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
               <td th:utext="${z.artHtml}"></td>
               <td th:utext="${z.artZusatzHtml}"></td>
               <td th:utext="${z.faktorHtml}"></td>
@@ -200,8 +202,8 @@
               </thead>
               <tbody>
                 <tr th:each="z : ${freibetragZeilen}" th:if="${z.typ == 'P'}" th:class="${z.rowClass}">
-                  <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
-                      th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+                  <td th:class="'sym ' + ${z.symbolClass}" th:utext="${z.symbol}"
+                      th:aria-label="${z.symbol.contains('fa-plus') ? 'Hinzugefügt' : (z.symbol.contains('fa-minus') ? 'Gelöscht' : (z.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
                   <td th:utext="${z.artHtml}"></td>
                   <td th:utext="${z.faktorHtml}"></td>
                   <td th:utext="${z.betragHtml}"></td>
@@ -232,8 +234,8 @@
               </thead>
               <tbody>
                 <tr th:each="z : ${freibetragZeilen}" th:if="${z.typ == 'F'}" th:class="${z.rowClass}">
-                  <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
-                      th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+                  <td th:class="'sym ' + ${z.symbolClass}" th:utext="${z.symbol}"
+                      th:aria-label="${z.symbol.contains('fa-plus') ? 'Hinzugefügt' : (z.symbol.contains('fa-minus') ? 'Gelöscht' : (z.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
                   <td th:utext="${z.artHtml}"></td>
                   <td th:utext="${z.faktorHtml}"></td>
                   <td th:utext="${z.betragHtml}"></td>
@@ -265,8 +267,8 @@
             </thead>
             <tbody>
               <tr th:each="z : ${kitaZeilen}" th:class="${z.rowClass}">
-                <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
-                    th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+                <td th:class="'sym ' + ${z.symbolClass}" th:utext="${z.symbol}"
+                    th:aria-label="${z.symbol.contains('fa-plus') ? 'Hinzugefügt' : (z.symbol.contains('fa-minus') ? 'Gelöscht' : (z.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
                 <td th:utext="${z.kindNameHtml}"></td>
                 <td th:utext="${z.betragHtml}"></td>
               </tr>

+ 6 - 4
src/main/resources/templates/miete.html

@@ -8,12 +8,14 @@
     <link rel="stylesheet" href="/css/header.css"/>
     <link rel="stylesheet" href="/css/legend.css"/>
     <link rel="stylesheet" href="/css/miete.css"/>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"/>
     <!-- JavaScript Module -->
     <script src="/js/cookie-utils.js"></script>
     <script src="/js/ajax-handlers.js"></script>
     <script src="/js/ui-updater-miete.js"></script>
     <script src="/js/interactions.js"></script>
     <script src="/js/color-scheme.js"></script>
+    <script src="/js/clipboard-utils.js"></script>
     <script src="/js/app.js"></script>
 </head>
 <body>
@@ -209,8 +211,8 @@
           </thead>
           <tbody>
             <tr th:each="z : ${untermieterZeilen}" th:class="${z.rowClass}">
-              <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
-                  th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+              <td th:class="'sym ' + ${z.symbolClass}" th:utext="${z.symbol}"
+                  th:aria-label="${z.symbol.contains('fa-plus') ? 'Hinzugefügt' : (z.symbol.contains('fa-minus') ? 'Gelöscht' : (z.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
               <td th:utext="${z.nameHtml}"></td>
               <td th:utext="${z.vornameHtml}"></td>
               <td th:utext="${z.mieteHtml}"></td>
@@ -252,8 +254,8 @@
           </thead>
           <tbody>
             <tr th:each="z : ${mitbewohnerZeilen}" th:class="${z.rowClass}">
-              <td class="sym" th:classappend="' ' + ${z.symbolClass}" th:text="${z.symbol}"
-                  th:aria-label="${z.symbol == '+' ? 'Hinzugefügt' : (z.symbol == '-' ? 'Gelöscht' : (z.symbol == '!' ? 'Geändert' : 'Unverändert'))}"></td>
+              <td th:class="'sym ' + ${z.symbolClass}" th:utext="${z.symbol}"
+                  th:aria-label="${z.symbol.contains('fa-plus') ? 'Hinzugefügt' : (z.symbol.contains('fa-minus') ? 'Gelöscht' : (z.symbol.contains('fa-exclamation') ? 'Geändert' : 'Unverändert'))}"></td>
               <td th:utext="${z.nameHtml}"></td>
               <td th:utext="${z.vornameHtml}"></td>
               <td th:utext="${z.entgeltHtml}"></td>