ソースを参照

Fianle Anpassung für Test

Leonard Schulz 1 年間 前
親
コミット
de69d08322

+ 43 - 0
src/main/java/com/tsystems/inwo/vgl/config/AuthenticationInterceptor.java

@@ -0,0 +1,43 @@
+package com.tsystems.inwo.vgl.config;
+
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+
+@Component
+public class AuthenticationInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String requestURI = request.getRequestURI();
+        
+        // Allow access to login page, static resources, and health endpoint
+        if (requestURI.startsWith("/login") || 
+            requestURI.startsWith("/static/") ||
+            requestURI.startsWith("/css/") ||
+            requestURI.startsWith("/js/") ||
+            requestURI.startsWith("/images/") ||
+            requestURI.equals("/health") ||
+            requestURI.equals("/actuator/health")) {
+            return true;
+        }
+
+        HttpSession session = request.getSession(false);
+        Boolean authenticated = null;
+        
+        if (session != null) {
+            authenticated = (Boolean) session.getAttribute("authenticated");
+        }
+        
+        if (authenticated != null && authenticated) {
+            return true;
+        }
+        
+        // Redirect to login page if not authenticated
+        response.sendRedirect("/login");
+        return false;
+    }
+}

+ 19 - 0
src/main/java/com/tsystems/inwo/vgl/config/WebConfig.java

@@ -0,0 +1,19 @@
+package com.tsystems.inwo.vgl.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private AuthenticationInterceptor authenticationInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(authenticationInterceptor)
+                .addPathPatterns("/**");
+    }
+}

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

@@ -737,16 +737,12 @@ public class VergleichDataExtractor {
         
         String zuschlägeHtml = htmlService.buildMieteFieldHtml(rightMiete, leftMiete,
             m -> {
-                double sonstZuschlag = m.path("sonstZuschlag").asDouble(0.0);
-                double moeblierung = m.path("moeblierung").asDouble(0.0);
-                double gewerbeZuschlag = m.path("gewerbeZuschlag").asDouble(0.0);
-                double umietZuschlag = m.path("umietZuschlag").asDouble(0.0);
-                double total = sonstZuschlag + moeblierung + gewerbeZuschlag + umietZuschlag;
-                return total > 0 ? String.format("%.2f €", total) : "";
+                double zuschläge = m.path("sonstWogFaehigBetrag").asDouble(0.0);
+                return zuschläge > 0 ? String.format("%.2f €", zuschläge) : "";
             }, hasChanges);
         
         String artZuschlagHtml = htmlService.buildMieteFieldHtml(rightMiete, leftMiete,
-            m -> m.path("sonstZuschlagBeschr").asText(""), hasChanges);
+            m -> m.path("sonstWogFaehigText").asText(""), hasChanges);
         
         String jhrlFernwaermeHtml = htmlService.buildMieteFieldHtml(rightMiete, leftMiete,
             m -> {

+ 36 - 0
src/main/java/com/tsystems/inwo/vgl/web/AuthController.java

@@ -0,0 +1,36 @@
+package com.tsystems.inwo.vgl.web;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import jakarta.servlet.http.HttpSession;
+
+@Controller
+public class AuthController {
+
+    @Value("${app.auth.password}")
+    private String authPassword;
+
+    @GetMapping("/login")
+    public String login(@RequestParam(required = false) String error, Model model) {
+        if (error != null) {
+            model.addAttribute("error", "Falsches Passwort. Bitte versuchen Sie es erneut.");
+        }
+        return "login";
+    }
+
+    @PostMapping("/login")
+    public String authenticate(@RequestParam String password, HttpSession session) {
+        if (authPassword.equals(password)) {
+            session.setAttribute("authenticated", true);
+            return "redirect:";
+        } else {
+            return "redirect:/login?error=true";
+        }
+    }
+
+}

+ 9 - 0
src/main/resources/application.properties

@@ -1 +1,10 @@
 server.compression.enabled=true
+
+server.address=127.0.0.1
+server.port=8080
+
+server.forward-headers-strategy=framework
+
+# Authentication settings
+app.auth.password=D1W0Vgl2025
+app.auth.session-timeout=7200

+ 405 - 0
src/main/resources/static/css/prototype.css

@@ -0,0 +1,405 @@
+/* Modal Styles */
+.tutorial-modal {
+    display: none;
+    position: fixed;
+    z-index: 1000;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    overflow: auto;
+    background-color: rgba(0,0,0,0.5);
+    animation: fadeIn 0.3s ease-in-out;
+}
+
+.tutorial-modal.show {
+    display: block;
+}
+
+.modal-content {
+    background-color: #fefefe;
+    margin: 2% auto;
+    padding: 0;
+    border: none;
+    border-radius: 12px;
+    width: 90%;
+    max-width: 1400px;
+    max-height: 100vh;
+    overflow: hidden;
+    box-shadow: 0 10px 30px rgba(0,0,0,0.3);
+    animation: slideIn 0.3s ease-out;
+}
+
+.modal-header {
+    background: linear-gradient(135deg, #007bff, #0056b3);
+    color: white;
+    padding: 1.5rem 2rem;
+    position: relative;
+}
+
+.modal-header h2 {
+    margin: 0;
+    font-size: 1.8rem;
+    font-weight: 600;
+}
+
+.close {
+    position: absolute;
+    top: 1rem;
+    right: 1.5rem;
+    color: white;
+    font-size: 2rem;
+    font-weight: bold;
+    cursor: pointer;
+    background: none;
+    border: none;
+    width: 40px;
+    height: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    transition: background-color 0.3s;
+}
+
+.close:hover,
+.close:focus {
+    background-color: rgba(255,255,255,0.2);
+}
+
+.modal-body {
+    padding: 2rem;
+    max-height: 60vh;
+    overflow-y: auto;
+}
+
+.video-container {
+    margin-bottom: 2rem;
+    text-align: center;
+}
+
+.video-placeholder {
+    width: 100%;
+    max-width: 600px;
+    height: 300px;
+    background: linear-gradient(135deg, #f8f9fa, #e9ecef);
+    border: 2px dashed #007bff;
+    border-radius: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0 auto;
+    color: #007bff;
+    font-size: 1.1rem;
+    font-weight: 500;
+}
+
+.tutorial-content {
+    display: grid;
+    gap: 1.5rem;
+}
+
+.tutorial-section {
+    background: #f8f9fa;
+    padding: 1.5rem;
+    border-radius: 8px;
+    border-left: 4px solid #007bff;
+}
+
+.tutorial-section h3 {
+    margin: 0 0 1rem 0;
+    color: #333;
+    font-size: 1.3rem;
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+}
+
+.tutorial-section .icon {
+    background: #007bff;
+    color: white;
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 0.9rem;
+}
+
+.tutorial-section p {
+    margin: 0 0 0.8rem 0;
+    line-height: 1.6;
+    color: #555;
+}
+
+.tutorial-section ul {
+    margin: 0;
+    padding-left: 1.5rem;
+    color: #555;
+}
+
+.tutorial-section li {
+    margin-bottom: 0.5rem;
+    line-height: 1.5;
+}
+
+.color-examples {
+    display: flex;
+    gap: 1rem;
+    margin-top: 1rem;
+    flex-wrap: wrap;
+}
+
+.color-example {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+    background: white;
+    padding: 0.5rem 1rem;
+    border-radius: 20px;
+    border: 1px solid #dee2e6;
+}
+
+.color-dot {
+    width: 16px;
+    height: 16px;
+    border-radius: 50%;
+    border: 1px solid #ccc;
+}
+
+.modal-footer {
+    background: #f8f9fa;
+    padding: 1rem 2rem;
+    border-top: 1px solid #dee2e6;
+    text-align: right;
+}
+
+.btn-primary {
+    background: #007bff;
+    color: white;
+    border: none;
+    padding: 0.75rem 1.5rem;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 1rem;
+    font-weight: 500;
+    transition: background-color 0.3s;
+}
+
+.btn-primary:hover {
+    background: #0056b3;
+}
+
+@keyframes fadeIn {
+    from { opacity: 0; }
+    to { opacity: 1; }
+}
+
+@keyframes slideIn {
+    from { transform: translateY(-50px); opacity: 0; }
+    to { transform: translateY(0); opacity: 1; }
+}
+
+.light-header {
+    background-color: #f8f9fa;
+    border-bottom: 1px solid #dee2e6;
+    padding: 1rem 2rem;
+    margin-bottom: 1rem;
+}
+
+.header-row-1 {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
+
+.kunde-logo-container {
+    height: 40px;
+}
+
+.kunde-logo-image {
+    height: 40px;
+    width: auto;
+}
+
+.page-title {
+    font-size: 1.5rem;
+    font-weight: 600;
+    color: #333;
+    margin: 0;
+}
+
+.container {
+    display: flex;
+    min-height: calc(100vh - 300px);
+    gap: 1rem;
+    padding: 0 2rem;
+}
+
+.sidebar {
+    width: 300px;
+    background-color: #f8f9fa;
+    padding: 1.5rem;
+    border-radius: 8px;
+    height: fit-content;
+    position: sticky;
+    top: 1rem;
+    display: flex;
+    flex-direction: column;
+}
+
+.main-content {
+    flex: 1;
+    min-width: 0;
+}
+
+.navigation {
+    margin-bottom: auto;
+}
+
+.navigation h3 {
+    margin: 0 0 1rem 0;
+    color: #333;
+    font-size: 1.1rem;
+}
+
+.nav-links {
+    display: flex;
+    flex-direction: column;
+    gap: 0.5rem;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+
+.nav-links a {
+    text-decoration: none;
+    color: #007bff;
+    padding: 0.8rem 1rem;
+    border-radius: 5px;
+    transition: all 0.3s;
+    border: 2px solid transparent;
+    display: block;
+}
+
+.nav-links a:hover {
+    background-color: #e9ecef;
+    transform: translateX(5px);
+}
+
+.nav-links a.active {
+    background-color: #007bff;
+    color: white;
+    border-color: #0056b3;
+    box-shadow: 0 2px 4px rgba(0,123,255,0.3);
+}
+
+.screenshot-container {
+    position: relative;
+    border: 2px solid #dee2e6;
+    border-radius: 8px;
+    overflow: hidden;
+    background-color: #fff;
+    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+}
+
+.screenshot {
+    width: 100%;
+    height: auto;
+    display: block;
+    position: relative;
+}
+
+.screenshot img {
+    width: 100%;
+    height: auto;
+    display: block;
+}
+
+.watermark {
+    position: absolute;
+    bottom: 10px;
+    left: 10px;
+    background-color: rgba(255, 255, 255, 0.9);
+    color: #ff0000;
+    padding: 0.5rem 1rem;
+    border-radius: 20px;
+    font-size: 0.9rem;
+    font-weight: 500;
+    border: 1px solid rgba(0, 0, 0, 0.1);
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+    z-index: 5;
+}
+
+.compare-button {
+    position: absolute;
+    top: 0.8%;
+    right: 14%;
+    background-color: #cce4c8;
+    color: #000;
+    padding: 0.5rem 0.5rem;
+    cursor: pointer;
+    font-size: 0.9rem;
+    font-weight: 600;
+    z-index: 10;
+    box-shadow: 0 3px 6px rgba(0,0,0,0.2);
+    transition: all 0.3s;
+    border: 1px solid rgb(0, 0, 0);
+}
+
+.compare-button-bottom {
+    position: absolute;
+    bottom: 1.5%;
+    right: 14%;
+    background-color: #cce4c8;
+    color: #000;
+    padding: 0.5rem 0.5rem;
+    cursor: pointer;
+    font-size: 0.9rem;
+    font-weight: 600;
+    z-index: 10;
+    box-shadow: 0 3px 6px rgba(0,0,0,0.2);
+    transition: all 0.3s;
+    border: 1px solid rgb(0, 0, 0);
+}
+
+.compare-button-bottom:hover {
+    background-color: #b9ceb5;
+    box-shadow: 0 4px 8px rgba(0,0,0,0.3);
+}
+
+.compare-button:hover {
+    background-color: #b9ceb5;
+    box-shadow: 0 4px 8px rgba(0,0,0,0.3);
+}
+
+.tutorial-restart-button {
+    margin-top: 2rem;
+    padding: 0.75rem 1rem;
+    background-color: #007bff;
+    color: white;
+    border: none;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 0.9rem;
+    font-weight: 500;
+    transition: all 0.3s;
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+    justify-content: center;
+    text-decoration: none;
+    box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
+}
+
+.tutorial-restart-button:hover {
+    background-color: #0056b3;
+    transform: translateY(-1px);
+    box-shadow: 0 4px 8px rgba(0, 123, 255, 0.4);
+}
+
+.tutorial-restart-button:active {
+    transform: translateY(0);
+    box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
+}

+ 1 - 1
src/main/resources/static/example-miete-realistic-alt.json

@@ -1 +1 @@
-{"stammakte":{"aktenzeichen":"01/01/119979","antragstellername":"Müller","antragstellerGeburtsdatum":"2000-01-01","anschrift":{"strasse":"Unter den Linden","hausnummer":"1","zusatz":"","plz":"10117","ort":"Berlin"},"vorgaenge":[{"stammvorgang":{"id":"1","vorgangstyp":"Erstantrag","vorgangsstatus":"unvollständig"},"zeitraeume":[{"id":"1","beginn":"2025-07-01","ende":"2026-06-30","miete":{"id":"1","miete":1600,"heizungP":null,"warmwasserP":null,"moeblierungP":null,"gewerbeZuschlagP":null,"sonstWogFaehigText":"","sonstZuschlagBeschr":"","energieP":null,"stellplatzP":null,"untermieter":[{"miId":"127560","lfdnr":1,"name":"Mieter","vorname":"Test","flaeche":10,"miete":200,"heizungP":null,"warmwasser":10,"warmwasserP":null,"moeblierungP":null,"sonst":10}],"mitbewohner":[]},"ausstattung":{"id":"1","flaeche":70,"heizung":"Zentralheizung","bad":"Mit Bad oder Duschraum","sozBauJn":false,"mietverhaeltnis":"Hauptm.und Nutzungsber.","eigentum":"","nameEig":"XYZ Vermietung GmbH","adresseEig":"","telefonEig":"12345","wofgJn":false,"foerderungJn":false}}]},{"stammvorgang":{"id":"2","vorgangstyp":"Weiterleistungsantrag","vorgangsstatus":"Antrag gelöscht"},"zeitraeume":[{"id":"1","beginn":"2026-07-01","ende":"2027-06-30","miete":{"id":"1","miete":1600,"heizungP":null,"warmwasserP":null,"moeblierungP":null,"gewerbeZuschlagP":null,"sonstWogFaehigText":"","sonstZuschlagBeschr":"","energie":100,"energieP":null,"stellplatz":125,"stellplatzP":null,"untermieter":[],"mitbewohner":[]},"ausstattung":{"id":"1","flaeche":70,"heizung":"Zentralheizung","bad":"Mit Bad oder Duschraum","sozBauJn":false,"mietverhaeltnis":"Hauptm.und Nutzungsber.","eigentum":"","nameEig":"XYZ Vermietung GmbH","adresseEig":"","telefonEig":"12345","wofgJn":false,"foerderungJn":false}}]}]}}
+{"stammakte":{"aktenzeichen":"01/01/120019","antragstellername":"Müller","antragstellerGeburtsdatum":"1990-01-01","anschrift":{"strasse":"Unter den Linden","hausnummer":"6","zusatz":"","plz":"10117","ort":"Berlin"},"vorgaenge":[{"stammvorgang":{"id":"1","vorgangstyp":"Erstantrag","vorgangsstatus":"unvollständig"},"zeitraeume":[{"id":"1","beginn":"2023-01-01","ende":"2023-12-31","miete":{"id":"1","miete":800,"heizungP":"Pauschal 1,25 EUR/m²","warmwasserP":"Pauschal in Abhängigkeit der Anzahl der Bewohner","moeblierungP":null,"gewerbeZuschlagP":null,"sonstWogFaehigText":"","sonstZuschlagBeschr":"","energieP":"Pauschal in Abhängigkeit der Anzahl der HHM","stellplatz":125,"stellplatzP":null,"untermieter":[],"mitbewohner":[{"miId":"127633","lfdnr":1,"name":"Meyer","vorname":"Sabine ","entgelt":200,"heizungP":"Pauschal 1,25 EUR/m²","warmwasser":50,"warmwasserP":null}]},"ausstattung":{"id":"1","flaeche":70,"heizung":"Fernheizung","bad":"Mit Bad oder Duschraum","sozBauJn":false,"mietverhaeltnis":"Hauptm.und Nutzungsber.","eigentum":"","nameEig":"Berliner Wohnungen GmBh","adresseEig":"Alexanderpl. 110178 Berlin","telefonEig":"030 12345","wofgJn":false,"foerderungJn":false}}]},{"stammvorgang":{"id":"2","vorgangstyp":"Weiterleistungsantrag","vorgangsstatus":"unvollständig"},"zeitraeume":[{"id":"1","beginn":"2024-01-01","ende":"2024-12-31","miete":{"id":"1","miete":1000,"heizung":100,"heizungP":null,"warmwasser":100,"warmwasserP":null,"moeblierungP":null,"gewerbeZuschlagP":null,"sonstWogFaehigText":"","sonstZuschlagBeschr":"","energie":100,"energieP":null,"stellplatz":125,"stellplatzP":null,"untermieter":[],"mitbewohner":[]},"ausstattung":{"id":"1","flaeche":100,"heizung":"Fernheizung","bad":"Mit Bad oder Duschraum","sozBauJn":false,"mietverhaeltnis":"Hauptm.und Nutzungsber.","eigentum":"","nameEig":"Berliner Wohnungen GmBh","adresseEig":"Alexanderpl. 110178 Berlin","telefonEig":"030 12345","wofgJn":false,"foerderungJn":false}}]}]}}

+ 1 - 1
src/main/resources/static/example-miete-realistic-neu.json

@@ -1 +1 @@
-{"stammakte":{"aktenzeichen":"01/01/119979","antragstellername":"Müller","antragstellerGeburtsdatum":"2000-01-01","anschrift":{"strasse":"Unter den Linden","hausnummer":"1","zusatz":"","plz":"10117","ort":"Berlin"},"vorgaenge":[{"stammvorgang":{"id":"3","vorgangstyp":"Weiterleistungsantrag","vorgangsstatus":"unvollständig"},"zeitraeume":[{"id":"1","beginn":"2026-07-01","ende":"2027-06-30","miete":{"id":"1","miete":1600,"sonstZuschlag":100,"beitragDritter":100,"heizung":200,"heizungP":null,"warmwasserP":null,"moeblierungP":null,"gewerbeZuschlagP":null,"gewerbeFlaeche":20,"sonstWogFaehigBetrag":100,"sonstWogFaehigText":"Gartenleistungen","sonstZuschlagBeschr":"Hausmeister","energie":100,"energieP":null,"stellplatz":160,"stellplatzP":null,"untermieter":[],"mitbewohner":[]},"ausstattung":{"id":"1","flaeche":70,"heizung":"Zentralheizung","bad":"Mit Bad oder Duschraum","sozBauJn":false,"mietverhaeltnis":"Hauptm.und Nutzungsber.","eigentum":"","fertigDat":"2000-01-01","nameEig":"XYZ Vermietung GmbH","adresseEig":"Neue Adresse ","telefonEig":"12345","wofgJn":false,"foerderungJn":false}}]}]}}
+{"stammakte":{"aktenzeichen":"01/01/120019","antragstellername":"Müller","antragstellerGeburtsdatum":"1990-01-01","anschrift":{"strasse":"Unter den Linden","hausnummer":"6","zusatz":"","plz":"10117","ort":"Berlin"},"vorgaenge":[{"stammvorgang":{"id":"3","vorgangstyp":"Weiterleistungsantrag","vorgangsstatus":"unvollständig"},"zeitraeume":[{"id":"1","beginn":"2025-01-01","ende":"2025-12-31","miete":{"id":"1","miete":1100,"sonstZuschlag":10,"heizung":100,"heizungP":null,"warmwasser":100,"warmwasserP":null,"moeblierungP":null,"gewerbeZuschlagP":null,"sonstWogFaehigBetrag":100,"sonstWogFaehigText":"Gartenleistungen","sonstZuschlagBeschr":"Fensterreinigung","energie":100,"energieP":null,"stellplatzP":"Garage, pauschal 36 EUR/mtl.","untermieter":[],"mitbewohner":[]},"ausstattung":{"id":"1","flaeche":100,"heizung":"Fernheizung","bad":"Mit Bad oder Duschraum","sozBauJn":false,"mietverhaeltnis":"Hauptm.und Nutzungsber.","eigentum":"","nameEig":"Berliner Wohnungen GmBh","adresseEig":"Alexanderpl. 110178 Berlin","telefonEig":"030 12345","wofgJn":false,"foerderungJn":false}}]}]}}

ファイルの差分が大きいため隠しています
+ 0 - 0
src/main/resources/static/example-realistic-alt.json


ファイルの差分が大きいため隠しています
+ 0 - 1
src/main/resources/static/example-realistic-neu.json


BIN
src/main/resources/static/images/hhm-einkommen-diwo.jpg


BIN
src/main/resources/static/images/miete-diwo.jpg


+ 128 - 0
src/main/resources/static/js/prototype.js

@@ -0,0 +1,128 @@
+let currentView = 'hhm';
+let compareWindow = null;
+
+// Tutorial-Funktionen
+function showTutorial() {
+    const modal = document.getElementById('tutorialModal');
+    modal.classList.add('show');
+    document.body.style.overflow = 'hidden'; // Verhindert Scrollen im Hintergrund
+}
+
+function closeTutorial() {
+    const modal = document.getElementById('tutorialModal');
+    modal.classList.remove('show');
+    document.body.style.overflow = ''; // Scrollen wieder aktivieren
+    
+    // Cookie setzen, damit Tutorial nicht erneut angezeigt wird
+    document.cookie = 'tutorialSeen=true; path=/; max-age=' + (365 * 24 * 60 * 60); // 1 Jahr
+}
+
+function hasTutorialBeenSeen() {
+    return document.cookie.split(';').some((item) => item.trim().startsWith('tutorialSeen='));
+}
+
+function restartTutorial() {
+    // Tutorial-Cookie löschen und Modal anzeigen
+    document.cookie = 'tutorialSeen=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;';
+    showTutorial();
+}
+
+// Modal schließen wenn außerhalb geklickt wird
+window.onclick = function(event) {
+    const modal = document.getElementById('tutorialModal');
+    if (event.target === modal) {
+        closeTutorial();
+    }
+}
+
+// ESC-Taste zum Schließen
+document.addEventListener('keydown', function(event) {
+    if (event.key === 'Escape') {
+        const modal = document.getElementById('tutorialModal');
+        if (modal.classList.contains('show')) {
+            closeTutorial();
+        }
+    }
+});
+
+function switchView(view) {
+    currentView = view;
+    
+    // Navigation updaten
+    document.querySelectorAll('.nav-link').forEach(link => {
+        link.classList.remove('active');
+    });
+    document.getElementById('nav-' + view).classList.add('active');
+    
+    // Screenshot updaten
+    updateScreenshot();
+    
+    // Wenn Vergleichsfenster offen ist, auch dort wechseln
+    if (compareWindow && !compareWindow.closed) {
+        updateCompareWindow();
+    }
+}
+
+function updateScreenshot() {
+    const screenshotImage = document.getElementById('screenshotImage');
+    
+    if (currentView === 'hhm') {
+        screenshotImage.src = '/images/hhm-einkommen-diwo.jpg';
+        screenshotImage.alt = 'Screenshot Haushaltsmitglieder/Einkommen';
+    } else {
+        screenshotImage.src = '/images/miete-diwo.jpg';
+        screenshotImage.alt = 'Screenshot Miete';
+    }
+}
+
+function openCompareView() {
+    if (compareWindow && !compareWindow.closed) {
+        compareWindow.focus();
+        return;
+    }
+    
+    const url = getCompareUrl();
+    compareWindow = window.open(url, 'compareView', 'width=1920,height=1200,scrollbars=yes,resizable=yes');
+    
+    if (compareWindow) {
+        compareWindow.focus();
+    }
+}
+
+function updateCompareWindow() {
+    if (compareWindow && !compareWindow.closed) {
+        const url = getCompareUrl();
+        compareWindow.location.href = url;
+    }
+}
+
+function getCompareUrl() {
+    const baseUrl = currentView === 'hhm' ? '/hhm-einkommen' : '/miete';
+    
+    // URL mit festen Beispieldaten (realistic) erstellen - keine Änderungsmöglichkeit
+    const params = new URLSearchParams();
+    params.append('loadExample', 'true');
+    params.append('exampleType', 'realistic');
+    params.append('validate', 'true');
+    params.append('kunde', 'Berlin');
+    
+    return `${baseUrl}?${params.toString()}`;
+}
+
+// Initial setup
+window.addEventListener('load', function() {
+    updateScreenshot();
+    
+    // Tutorial anzeigen wenn noch nicht gesehen
+    if (!hasTutorialBeenSeen()) {
+        // Kleine Verzögerung für bessere UX
+        setTimeout(showTutorial, 500);
+    }
+});
+
+// Überprüfen, ob Popup-Fenster noch offen ist
+setInterval(function() {
+    if (compareWindow && compareWindow.closed) {
+        compareWindow = null;
+    }
+}, 1000);

+ 99 - 0
src/main/resources/templates/login.html

@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Anmeldung - Vergleichsansicht</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            background-color: #f5f5f5;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            min-height: 100vh;
+            margin: 0;
+        }
+
+        .login-box {
+            background: white;
+            padding: 2rem;
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+            width: 300px;
+        }
+
+        h1 {
+            text-align: center;
+            color: #333;
+            margin-bottom: 1.5rem;
+        }
+
+        .form-group {
+            margin-bottom: 1rem;
+        }
+
+        label {
+            display: block;
+            margin-bottom: 0.5rem;
+            color: #555;
+        }
+
+        input[type="password"] {
+            width: 100%;
+            padding: 0.75rem;
+            border: 1px solid #ddd;
+            border-radius: 4px;
+            font-size: 1rem;
+            box-sizing: border-box;
+        }
+
+        input[type="password"]:focus {
+            outline: none;
+            border-color: #007bff;
+        }
+
+        .btn {
+            width: 100%;
+            padding: 0.75rem;
+            background-color: #007bff;
+            color: white;
+            border: none;
+            border-radius: 4px;
+            font-size: 1rem;
+            cursor: pointer;
+        }
+
+        .btn:hover {
+            background-color: #0056b3;
+        }
+
+        .error {
+            color: #dc3545;
+            margin-bottom: 1rem;
+            padding: 0.5rem;
+            background-color: #f8d7da;
+            border: 1px solid #f5c6cb;
+            border-radius: 4px;
+        }
+    </style>
+</head>
+<body>
+    <div class="login-box">
+        <h1>Vergleichsansicht</h1>
+        
+        <div th:if="${error}" class="error">
+            <span th:text="${error}">Fehler</span>
+        </div>
+
+        <form method="post" action="/login">
+            <div class="form-group">
+                <label for="password">Passwort:</label>
+                <input type="password" id="password" name="password" required autofocus>
+            </div>
+            
+            <button type="submit" class="btn">Anmelden</button>
+        </form>
+    </div>
+</body>
+</html>

+ 62 - 288
src/main/resources/templates/prototype.html

@@ -6,197 +6,61 @@
     <link rel="stylesheet" href="/css/global.css"/>
     <link rel="stylesheet" href="/css/header.css"/>
     <link rel="stylesheet" href="/css/footer.css"/>
-    <style>
-        .light-header {
-            background-color: #f8f9fa;
-            border-bottom: 1px solid #dee2e6;
-            padding: 1rem 2rem;
-            margin-bottom: 1rem;
-        }
-        
-        .header-row-1 {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-        }
-        
-        .kunde-logo-container {
-            height: 40px;
-        }
-        
-        .kunde-logo-image {
-            height: 40px;
-            width: auto;
-        }
-        
-        .page-title {
-            font-size: 1.5rem;
-            font-weight: 600;
-            color: #333;
-            margin: 0;
-        }
-        
-        .container {
-            display: flex;
-            min-height: calc(100vh - 300px);
-            gap: 1rem;
-            padding: 0 2rem;
-        }
-        
-        .sidebar {
-            width: 300px;
-            background-color: #f8f9fa;
-            padding: 1.5rem;
-            border-radius: 8px;
-            height: fit-content;
-            position: sticky;
-            top: 1rem;
-            display: flex;
-            flex-direction: column;
-        }
-        
-        .main-content {
-            flex: 1;
-            min-width: 0;
-        }
-        
-        .navigation {
-            margin-bottom: auto;
-        }
-        
-        .navigation h3 {
-            margin: 0 0 1rem 0;
-            color: #333;
-            font-size: 1.1rem;
-        }
-        
-        .nav-links {
-            display: flex;
-            flex-direction: column;
-            gap: 0.5rem;
-            list-style: none;
-            margin: 0;
-            padding: 0;
-        }
-        
-        .nav-links a {
-            text-decoration: none;
-            color: #007bff;
-            padding: 0.8rem 1rem;
-            border-radius: 5px;
-            transition: all 0.3s;
-            border: 2px solid transparent;
-            display: block;
-        }
-        
-        .nav-links a:hover {
-            background-color: #e9ecef;
-            transform: translateX(5px);
-        }
-        
-        .nav-links a.active {
-            background-color: #007bff;
-            color: white;
-            border-color: #0056b3;
-            box-shadow: 0 2px 4px rgba(0,123,255,0.3);
-        }
-        
-        .controls {
-            border-top: 1px solid #dee2e6;
-            padding-top: 1rem;
-            margin-top: 1rem;
-        }
-        
-        .controls h3 {
-            margin: 0 0 0.8rem 0;
-            color: #333;
-            font-size: 1rem;
-        }
-        
-        .controls label {
-            display: block;
-            margin-bottom: 0.4rem;
-            font-weight: 500;
-            color: #555;
-            font-size: 0.9rem;
-        }
-        
-        .controls select {
-            width: 100%;
-            padding: 0.5rem;
-            border: 1px solid #ccc;
-            border-radius: 4px;
-            margin-bottom: 0.8rem;
-            font-size: 0.85rem;
-        }
-        
-        .controls select:focus {
-            outline: none;
-            border-color: #007bff;
-            box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
-        }
-        
-        .screenshot-container {
-            position: relative;
-            border: 2px solid #dee2e6;
-            border-radius: 8px;
-            overflow: hidden;
-            background-color: #fff;
-            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
-        }
-        
-        .screenshot {
-            width: 100%;
-            height: auto;
-            display: block;
-            position: relative;
-        }
-        
-        .screenshot img {
-            width: 100%;
-            height: auto;
-            display: block;
-        }
-        
-        .watermark {
-            position: absolute;
-            bottom: 50px;
-            right: 20px;
-            background-color: rgba(255, 255, 255, 0.9);
-            color: #ff0000;
-            padding: 0.5rem 1rem;
-            border-radius: 20px;
-            font-size: 0.9rem;
-            font-weight: 500;
-            border: 1px solid rgba(0, 0, 0, 0.1);
-            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-            z-index: 5;
-        }
-        
-        .compare-button {
-            position: absolute;
-            top: 100px;
-            right: 200px;
-            background-color: #28a745;
-            color: white;
-            border: none;
-            padding: 0.8rem 1.5rem;
-            border-radius: 5px;
-            cursor: pointer;
-            font-size: 1rem;
-            font-weight: 600;
-            z-index: 10;
-            box-shadow: 0 3px 6px rgba(0,0,0,0.2);
-            transition: all 0.3s;
-        }
-        
-        .compare-button:hover {
-            background-color: #218838;
-            box-shadow: 0 4px 8px rgba(0,0,0,0.3);
-        }
-    </style>
+    <link rel="stylesheet" href="/css/prototype.css"/>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"/>
 </head>
 <body>
+    <!-- Tutorial Modal -->
+    <div id="tutorialModal" class="tutorial-modal">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h2><i class="fas fa-graduation-cap"></i> Anleitung zur Vergleichsansicht</h2>
+                <button class="close" onclick="closeTutorial()">&times;</button>
+            </div>
+            <div class="modal-body">
+                <div class="video-container">
+                    <div class="video-placeholder">
+                        <div>
+                            <i class="fas fa-play-circle" style="font-size: 3rem; margin-bottom: 1rem; display: block;"></i>
+                            Erklärvideo wird hier eingebettet
+                        </div>
+                    </div>
+                </div>
+                
+                <div class="tutorial-content">
+                    <div class="tutorial-section">
+                        <h3><span class="icon">1</span>Vergleichsansicht öffnen</h3>
+                        <p>Klicken Sie auf den grünen Button <strong>"Vergleichsansicht"</strong> oben rechts oder unten rechts im Bildschirmbereich. Dies öffnet ein neues Fenster mit der interaktiven Vergleichsansicht. Es öffnet sich ein <strong>neues Fenster</strong>, dieses können Sie auf einen 2. Bildschirm verschieben.</p>
+                        <p>Die Vergleichsansicht zeigt Ihnen die Unterschiede zwischen einem alten Vorgang und dem aktuellen Vorgang, der hier in DiWo exemplarisch geöffnet ist. In der Vergleichsansicht können Sie oben rechts den <strong>Vergleichsvorgang bzw. Vergleichszeitraum</strong> auswählen und somit die Grundlage für den Vergleich festlegen.</p>
+                    </div>
+
+                    <div class="tutorial-section">
+                        <h3><span class="icon">2</span>Navigation zwischen Bereichen</h3>
+                        <p>In der linken Sidebar können Sie zwischen verschiedenen Bereichen wechseln:</p>
+                        <ul>
+                            <li><strong>Haushaltsmitglieder/Einkommen:</strong> Vergleich der Einkommenssituation und Haushaltszusammensetzung. Sie können in der Vergleichsansicht die einzelnen Haushaltsmitglieder wie in DiWo auswählen (Zeilen anklicken) und die Detaildaten anzeigen (aufklappen).</li>
+                            <li><strong>Miete:</strong> Vergleich der Mietkosten und Wohnsituation</li>
+                        </ul>
+                        <p>Der ausgewählte Bereich wird blau hervorgehoben und der entsprechende Screenshot wird angezeigt. Die Vergleichsansicht wechselt zeitgleich zu den entsprechenden Seiten im Beispiel-DiWo.</p>
+                    </div>
+
+                    <div class="tutorial-section">
+                        <h3><span class="icon">3</span>Farbschema wechseln</h3>
+                        <p>In der Vergleichsansicht können Sie das Farbschema über die Schaltflächen in der Legende wechseln. Standardmäßig sind die Ampelfarben ausgewählt, alternativ gibt es eine Rot-Grün-Schwäche-Anzeige, eine Ampelanzeige mit maximalem Kontrast sowie eine Kombination aus Pastellfarben.</p>
+                        <p>Das gewählte Farbschema wird automatisch gespeichert und bei Ihrem nächsten Besuch wiederhergestellt.</p>
+                    </div>
+
+            
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button class="btn-primary" onclick="closeTutorial()">
+                    <i class="fas fa-check"></i> Verstanden, Los geht's!
+                </button>
+            </div>
+        </div>
+    </div>
+
     <!-- Light Header -->
     <header class="light-header">
         <div class="header-row-1">
@@ -222,20 +86,18 @@
                 </ul>
             </nav>
             
-            <div class="controls">
-                <h3>Einstellungen</h3>
-                <label for="exampleTypeSelect">Beispieltyp:</label>
-                <select id="exampleTypeSelect" onchange="updateExampleType()">
-                    <option value="realistic">Realistische Änderungen</option>
-                    <option value="many">Viele Änderungen</option>
-                </select>
-            </div>
+            <!-- Tutorial Restart Button -->
+            <button class="tutorial-restart-button" onclick="restartTutorial()" title="Hilfe erneut anzeigen">
+                <i class="fas fa-graduation-cap"></i>
+                Hilfe erneut anzeigen
+            </button>
         </aside>
         
         <main class="main-content">
             <div class="screenshot-container">
                 <div class="watermark">Bildschirmfoto</div>
-                <button class="compare-button" onclick="openCompareView()">Vergleichsansicht</button>
+                <button class="compare-button" onclick="openCompareView()"><i class="fa-solid fa-code-compare"></i> Vergleichsansicht</button>
+                <button class="compare-button-bottom" onclick="openCompareView()"><i class="fa-solid fa-code-compare"></i> Vergleichsansicht</button>
                 <div class="screenshot" id="screenshot">
                     <img src="/images/hhm-einkommen-diwo.jpg" alt="Screenshot Haushaltsmitglieder/Einkommen" id="screenshotImage">
                 </div>
@@ -243,96 +105,8 @@
         </main>
     </div>
 
-    <script>
-        let currentView = 'hhm';
-        let currentExampleType = 'realistic';
-        let compareWindow = null;
-        
-        function switchView(view) {
-            currentView = view;
-            
-            // Navigation updaten
-            document.querySelectorAll('.nav-link').forEach(link => {
-                link.classList.remove('active');
-            });
-            document.getElementById('nav-' + view).classList.add('active');
-            
-            // Screenshot updaten
-            updateScreenshot();
-            
-            // Wenn Vergleichsfenster offen ist, auch dort wechseln
-            if (compareWindow && !compareWindow.closed) {
-                updateCompareWindow();
-            }
-        }
-        
-        function updateExampleType() {
-            currentExampleType = document.getElementById('exampleTypeSelect').value;
-            
-            // Wenn Vergleichsfenster offen ist, auch dort updaten
-            if (compareWindow && !compareWindow.closed) {
-                updateCompareWindow();
-            }
-        }
-        
-        function updateScreenshot() {
-            const screenshotImage = document.getElementById('screenshotImage');
-            
-            if (currentView === 'hhm') {
-                screenshotImage.src = '/images/hhm-einkommen-diwo.jpg';
-                screenshotImage.alt = 'Screenshot Haushaltsmitglieder/Einkommen';
-            } else {
-                screenshotImage.src = '/images/miete-diwo.jpg';
-                screenshotImage.alt = 'Screenshot Miete';
-            }
-        }
-        
-        function openCompareView() {
-            if (compareWindow && !compareWindow.closed) {
-                compareWindow.focus();
-                return;
-            }
-            
-            const url = getCompareUrl();
-            compareWindow = window.open(url, 'compareView', 'width=1400,height=900,scrollbars=yes,resizable=yes');
-            
-            if (compareWindow) {
-                compareWindow.focus();
-            }
-        }
-        
-        function updateCompareWindow() {
-            if (compareWindow && !compareWindow.closed) {
-                const url = getCompareUrl();
-                compareWindow.location.href = url;
-            }
-        }
-        
-        function getCompareUrl() {
-            const baseUrl = currentView === 'hhm' ? '/hhm-einkommen' : '/miete';
-            
-            // URL mit vorgeladenen JSON-Daten erstellen
-            const params = new URLSearchParams();
-            params.append('loadExample', 'true');
-            params.append('exampleType', currentExampleType);
-            params.append('validate', 'true');
-            params.append('kunde', 'Berlin');
-            
-            return `${baseUrl}?${params.toString()}`;
-        }
-        
-        // Initial setup
-        window.addEventListener('load', function() {
-            updateScreenshot();
-        });
-        
-        // Überprüfen, ob Popup-Fenster noch offen ist
-        setInterval(function() {
-            if (compareWindow && compareWindow.closed) {
-                compareWindow = null;
-            }
-        }, 1000);
-    </script>
+    <!-- JavaScript-Datei einbinden -->
+    <script src="/js/prototype.js"></script>
 
     <!-- Footer-Fragment einbinden -->
     <div th:replace="~{fragments/footer :: footer}"></div>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません