From 7193c9a7878f9c82b7033b5661a13c7d32ef067a Mon Sep 17 00:00:00 2001 From: Linus Westling Date: Thu, 23 Apr 2026 16:05:13 +0200 Subject: [PATCH 1/4] Fixed the landing page / presentation --- .../infrastructure/config/SecurityConfig.java | 6 +- .../presentation/web/UiController.java | 5 + src/main/resources/static/app.css | 857 +++++++++++++++++- src/main/resources/static/app.js | 253 ++++++ src/main/resources/templates/landing.html | 148 +++ start-local.ps1 | 2 +- 6 files changed, 1261 insertions(+), 10 deletions(-) create mode 100644 src/main/resources/static/app.js create mode 100644 src/main/resources/templates/landing.html diff --git a/src/main/java/org/example/projektarendehantering/infrastructure/config/SecurityConfig.java b/src/main/java/org/example/projektarendehantering/infrastructure/config/SecurityConfig.java index 2325e0a..98185d1 100644 --- a/src/main/java/org/example/projektarendehantering/infrastructure/config/SecurityConfig.java +++ b/src/main/java/org/example/projektarendehantering/infrastructure/config/SecurityConfig.java @@ -28,7 +28,7 @@ public SecurityConfig(LocalUserDetailsService localUserDetailsService) { public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomOAuth2UserService customOAuth2UserService) throws Exception { http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/login**", "/register", "/error**", "/static/**", "/app.css", "/app.js", "/webjars/**").permitAll() + .requestMatchers("/", "/login**", "/register", "/error**", "/static/**", "/app.css", "/app.js", "/webjars/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 @@ -36,12 +36,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomOAuth2Us .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService) ) - .defaultSuccessUrl("/", true) + .defaultSuccessUrl("/home", true) ) .formLogin(form -> form .loginPage("/login") .loginProcessingUrl("/login") - .defaultSuccessUrl("/", true) + .defaultSuccessUrl("/home", true) .failureUrl("/login?error=true") ) .logout(logout -> logout diff --git a/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java b/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java index ac9f866..57efd1d 100644 --- a/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java +++ b/src/main/java/org/example/projektarendehantering/presentation/web/UiController.java @@ -47,6 +47,11 @@ public String addNote(@PathVariable UUID caseId, @RequestParam("content") String } @GetMapping("/") + public String landing() { + return "landing"; + } + + @GetMapping("/home") public String index() { return "index"; } diff --git a/src/main/resources/static/app.css b/src/main/resources/static/app.css index 28dd506..90cb613 100644 --- a/src/main/resources/static/app.css +++ b/src/main/resources/static/app.css @@ -19,9 +19,7 @@ html, body { height: 100%; } body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji", "Segoe UI Emoji"; - background: radial-gradient(1200px 700px at 20% 0%, rgba(110, 168, 255, 0.25), transparent 60%), - radial-gradient(900px 500px at 70% 20%, rgba(142, 97, 255, 0.18), transparent 60%), - var(--bg); + background: var(--bg); color: var(--text); line-height: 1.5; } @@ -101,7 +99,7 @@ p { margin: 0 0 14px; color: var(--muted); max-width: 75ch; } display: block; text-decoration: none; color: var(--text); - background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)); + background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; @@ -110,13 +108,13 @@ p { margin: 0 0 14px; color: var(--muted); max-width: 75ch; } } .card:hover { border-color: rgba(110, 168, 255, 0.45); - background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03)); + background: rgba(255,255,255,0.05); transform: translateY(-1px); } .card p { margin: 6px 0 0; } .panel { - background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)); + background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px; @@ -256,15 +254,862 @@ textarea.input { padding: 12px; resize: vertical; } } .footer-row { display: flex; justify-content: space-between; align-items: center; } +.landing-page { + display: grid; + gap: 18px; +} + +.landing-progress { + position: sticky; + top: 0; + z-index: 20; + width: 100%; + height: 3px; + background: rgba(255, 255, 255, 0.08); + border-radius: 999px; + overflow: hidden; +} + +.landing-progress-bar { + width: 0; + height: 100%; + background: linear-gradient(90deg, var(--accent), var(--accent-strong)); + transition: width 180ms ease-out; +} + +.landing-topbar { + display: flex; + justify-content: flex-end; + align-items: center; +} + +.landing-login { + min-width: 112px; +} + +.landing-hero { + text-align: center; + display: grid; + gap: 10px; + justify-items: center; + padding: 28px; +} + +.landing-hero--presentation { + gap: 12px; + padding: 32px 28px; +} + +.landing-hero-meta { + display: inline-flex; + gap: 8px; + align-items: center; +} + +.landing-hero-time { + font-size: 12px; + color: var(--muted); + border: 1px solid var(--border); + border-radius: 999px; + padding: 4px 10px; +} + +.landing-lead { + font-size: 17px; + max-width: 72ch; +} + +.landing-hero-footnote { + margin: 2px 0 0; + font-size: 13px; + color: var(--muted); +} + +.landing-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; + justify-content: center; +} + +.landing-cta-primary { min-width: 170px; } +.landing-cta-secondary { min-width: 160px; } + +.landing-actions .button { + transform: translateY(0); +} + +.landing-actions .button:hover { + transform: translateY(-1px); +} + +.landing-actions .button:active { + transform: translateY(0); +} + +.landing-nav { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.landing-nav-link { + text-decoration: none; + color: var(--muted); + border: 1px solid var(--border); + border-radius: 999px; + padding: 6px 12px; + font-size: 13px; + transition: all var(--transition-fast); +} + +.landing-nav-link:hover { + color: var(--text); + border-color: rgba(110, 168, 255, 0.55); + background: rgba(110, 168, 255, 0.12); +} + +.landing-nav-link.active { + color: var(--text); + border-color: rgba(143, 190, 255, 0.8); + background: rgba(110, 168, 255, 0.22); +} + +.landing-section { + display: grid; + gap: 14px; +} + +.landing-section-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + flex-wrap: wrap; +} + +.landing-section-kicker { + margin: 0; + font-size: 14px; + color: var(--muted); +} + +.landing-columns { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.landing-subpanel { + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.03); +} + +.landing-subpanel p { + margin-bottom: 0; +} + +.landing-subpanel--problem { border-color: rgba(255,255,255,0.14); } +.landing-subpanel--impact { border-color: rgba(255,110,138,0.28); } +.landing-subpanel--solution { border-color: rgba(110,168,255,0.45); } + +.landing-subpanel { + transition: border-color var(--transition-fast), background var(--transition-fast), transform var(--transition-fast); +} + +.landing-subpanel:hover { + background: rgba(255,255,255,0.05); + transform: translateY(-2px); +} + +.landing-status-flow { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.landing-chip { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + border: 1px solid rgba(110, 168, 255, 0.55); + background: rgba(110, 168, 255, 0.16); + color: var(--text); + min-height: 30px; + padding: 0 12px; + font-size: 13px; + font-weight: 600; +} + +.landing-flow-arrow { + color: var(--muted); + font-weight: 700; +} + +.section-intro { + margin: 0; + color: var(--text); + font-size: 15px; +} + +.lifecycle-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 12px; + margin: 0; + padding: 0; + list-style: none; +} + +.lifecycle-card { + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.03); +} + +.step-kicker { + margin: 0 0 4px; + color: var(--muted); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 11px; +} + +.status-legend { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.chip { + display: inline-flex; + align-items: center; + min-height: 28px; + border-radius: 999px; + padding: 0 10px; + border: 1px solid var(--border); + font-size: 12px; + color: var(--text); +} + +.chip-owner { + border-color: rgba(110, 168, 255, 0.4); + background: rgba(110, 168, 255, 0.14); +} + +.chip-status-open { + border-color: rgba(110, 168, 255, 0.4); + background: rgba(110, 168, 255, 0.14); +} + +.chip-status-progress { + border-color: rgba(255, 210, 120, 0.45); + background: rgba(255, 210, 120, 0.14); +} + +.chip-status-closed { + border-color: rgba(123, 228, 149, 0.45); + background: rgba(123, 228, 149, 0.14); +} + +.roles-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.role-card { + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.03); +} + +.role-summary { + margin: 0 0 10px; +} + +.chip-row { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.chip-allow::before { + content: "✓"; + margin-right: 6px; + opacity: 0.9; +} + +.chip-deny { + border-color: rgba(255, 110, 138, 0.35); + background: rgba(255, 110, 138, 0.12); +} + +.chip-deny::before { + content: "—"; + margin-right: 6px; + opacity: 0.9; +} + +.module-head { + display: grid; + gap: 6px; + margin-bottom: 6px; +} + +.module-intro { + margin: 0; + color: var(--text); + font-size: 15px; + max-width: 70ch; +} + +.trust-strip { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.trust-chip { + display: inline-flex; + align-items: center; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + border: 1px solid rgba(123, 228, 149, 0.45); + background: rgba(123, 228, 149, 0.12); + color: #dff7e7; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.02em; +} + +.proof-card h3, +.evidence-note h3 { + margin-bottom: 10px; +} + +.proof-list { + margin: 0; + padding-left: 18px; + color: var(--muted); +} + +.proof-list li + li { + margin-top: 6px; +} + +.evidence-note { + border-color: rgba(110, 168, 255, 0.42); + background: linear-gradient(180deg, rgba(110, 168, 255, 0.12), rgba(110, 168, 255, 0.06)); +} + +.landing-section-proof { + gap: 12px; +} + +.landing-proof-lead { + margin: 0; + font-size: 16px; + color: var(--text); + max-width: 78ch; +} + +.landing-proof-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.landing-proof-card { + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.03); + box-shadow: inset 0 0 0 1px rgba(110, 168, 255, 0.06); +} + +.landing-proof-card p { + margin-bottom: 0; +} + +.landing-close { + display: grid; + gap: 8px; + justify-items: start; +} + +.landing-close-cta { + border-color: rgba(110, 168, 255, 0.55); + background: rgba(255,255,255,0.02); + gap: 10px; +} + +.landing-close-kicker { + margin: 0; + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted); +} + +.landing-close-lead { + margin: 0; + color: var(--text); + font-size: 16px; + max-width: 72ch; +} + +.landing-close-points { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.landing-close-actions { + margin-top: 4px; +} + +.button-strong { + border-color: rgba(143, 190, 255, 0.95); + background: linear-gradient(180deg, rgba(110, 168, 255, 0.42), rgba(110, 168, 255, 0.24)); + box-shadow: 0 0 0 1px rgba(143, 190, 255, 0.25), 0 8px 20px rgba(110, 168, 255, 0.18); +} + +.button-strong:hover { + background: linear-gradient(180deg, rgba(110, 168, 255, 0.5), rgba(110, 168, 255, 0.3)); +} + +.js-reveal { + opacity: 1; + transform: none; +} + +.js-enhanced .js-reveal { + opacity: 0; + transform: translateY(18px); +} + +.js-enhanced .js-reveal.revealed { + opacity: 1; + transform: translateY(0); + transition: opacity 360ms ease, transform 360ms ease; +} + +@keyframes ctaPulse { + 0%, 100% { transform: translateY(0); } + 35% { transform: translateY(-1px) scale(1.01); } + 70% { transform: translateY(0); } +} + +.cta-attention { + animation: ctaPulse 500ms ease-in-out 2; +} + @media (max-width: 700px) { .container { width: min(1140px, calc(100% - 24px)); } .page { padding-top: 24px; } .page-title { align-items: flex-start; flex-direction: column; } .auth-label { margin-right: 0; display: block; margin-bottom: 8px; } + .landing-columns { grid-template-columns: 1fr; } + .landing-nav { gap: 6px; } + .landing-nav-link { font-size: 12px; } + .landing-flow-arrow { display: none; } + .landing-hero { padding: 22px; } + .roles-grid { grid-template-columns: 1fr; } + .landing-proof-grid { grid-template-columns: 1fr; } } @media (max-width: 980px) { .header-row { grid-template-columns: 1fr; align-items: start; } .cards { grid-template-columns: 1fr; } .auth-grid { grid-template-columns: 1fr; } + .landing-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .landing-proof-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .lifecycle-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +@media (prefers-reduced-motion: reduce) { + .landing-progress-bar, + .landing-nav-link, + .js-reveal.revealed { + transition: none; + } + + .js-reveal { + opacity: 1; + transform: none; + } +} + +/* Calm presentation overrides */ +.landing-page { + gap: 24px; +} + +.landing-section, +#hero-cta, +#final-cta { + padding: 24px; +} + +.landing-nav-link { + background: rgba(255, 255, 255, 0.01); +} + +.landing-section-kicker, +.module-intro, +.landing-close-lead { + max-width: 58ch; +} + +.segmented-tabs { + display: inline-flex; + gap: 6px; + padding: 4px; + border-radius: 999px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.02); +} + +.segmented-tabs__btn { + border: 0; + border-radius: 999px; + min-height: 32px; + padding: 0 12px; + color: var(--muted); + background: transparent; + cursor: pointer; +} + +.segmented-tabs__btn.is-active { + color: var(--text); + background: rgba(110, 168, 255, 0.2); +} + +.tab-panel { + margin-top: 12px; +} + +.icon-card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); + gap: 12px; +} + +.icon-card { + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.03); +} + +.icon-card h3 { + margin: 0; +} + +.icon-dot { + display: inline-grid; + place-items: center; + width: 26px; + height: 26px; + border-radius: 8px; + margin-bottom: 8px; + font-weight: 700; + background: rgba(255, 255, 255, 0.08); +} + +.icon-card--alert .icon-dot { + background: rgba(255, 110, 138, 0.2); +} + +.icon-card--good .icon-dot { + background: rgba(123, 228, 149, 0.2); +} + +.flow-timeline { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; + margin: 0; + padding: 0; + list-style: none; +} + +.flow-step { + width: 100%; + min-height: 36px; + border-radius: 10px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.03); + color: var(--text); + cursor: pointer; +} + +.flow-step.is-active { + border-color: rgba(143, 190, 255, 0.8); + background: rgba(110, 168, 255, 0.2); +} + +.flow-detail { + margin-top: 12px; + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.02); +} + +.role-tabs { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.role-tabs__btn { + border: 1px solid var(--border); + border-radius: 999px; + min-height: 32px; + padding: 0 12px; + background: rgba(255, 255, 255, 0.02); + color: var(--muted); + cursor: pointer; +} + +.role-tabs__btn.is-active { + color: var(--text); + border-color: rgba(143, 190, 255, 0.8); + background: rgba(110, 168, 255, 0.2); +} + +.role-panel { + margin-top: 12px; + border: 1px solid var(--border); + border-radius: 12px; + padding: 14px; + background: rgba(255, 255, 255, 0.02); +} + +.metrics-row, +.permission-chips { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.metric-chip { + display: inline-flex; + align-items: center; + min-height: 28px; + border-radius: 999px; + padding: 0 10px; + border: 1px solid var(--border); + font-size: 12px; + background: rgba(255, 255, 255, 0.03); +} + +.permission-chip--allow { + border-color: rgba(123, 228, 149, 0.45); + background: rgba(123, 228, 149, 0.14); +} + +.permission-chip--deny { + border-color: rgba(255, 110, 138, 0.35); + background: rgba(255, 110, 138, 0.12); +} + +.accordion { + display: grid; + gap: 8px; +} + +.accordion__trigger { + text-align: left; + min-height: 38px; + border-radius: 10px; + border: 1px solid var(--border); + background: rgba(255, 255, 255, 0.03); + color: var(--text); + padding: 0 12px; + cursor: pointer; +} + +.accordion__panel { + margin-top: -2px; + margin-bottom: 4px; + border-left: 2px solid rgba(110, 168, 255, 0.45); + padding: 8px 12px; + color: var(--muted); +} + +.lite-details { + margin-top: 10px; +} + +.lite-details summary { + cursor: pointer; + color: var(--text); +} + +@media (max-width: 900px) { + .flow-timeline { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +/* Cohesive layout fix: avoid split panel feeling */ +.landing-page { + border: 0; + border-radius: 0; + background: none; + padding: 0; +} + +.landing-progress { + position: relative; + top: auto; +} + +.landing-page > .panel { + background: rgba(255,255,255,0.03); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); + padding: 18px; +} + +.landing-page > .panel + .panel { + margin-top: 0; + border-top: 0; +} + +.landing-close-cta { + background: rgba(255,255,255,0.03); + border-color: var(--border); +} + +/* Presentation pop polish: higher contrast + playful depth */ +.landing-page > .panel { + border-color: rgba(143, 190, 255, 0.26); + box-shadow: 0 12px 30px rgba(2, 8, 28, 0.42); +} + +.landing-page > .panel:hover { + border-color: rgba(143, 190, 255, 0.48); +} + +h1, h2 { + color: #f4f7ff; +} + +.landing-lead, +.module-intro, +.landing-section-kicker, +.landing-close-lead { + color: rgba(236, 242, 255, 0.88); +} + +.notice-badge, +.landing-hero-time { + border-color: rgba(143, 190, 255, 0.66); + background: rgba(110, 168, 255, 0.26); + color: #f3f7ff; +} + +.landing-nav-link { + color: rgba(239, 244, 255, 0.9); + border-color: rgba(143, 190, 255, 0.25); + background: rgba(110, 168, 255, 0.07); +} + +.landing-nav-link:hover, +.landing-nav-link.active { + border-color: rgba(143, 190, 255, 0.8); + background: rgba(110, 168, 255, 0.26); + box-shadow: 0 0 0 1px rgba(143, 190, 255, 0.2) inset; +} + +.button { + border-color: rgba(143, 190, 255, 0.78); + background: rgba(110, 168, 255, 0.33); +} + +.button:hover { + background: rgba(110, 168, 255, 0.46); + box-shadow: 0 8px 18px rgba(67, 131, 255, 0.28); +} + +.button-secondary { + border-color: rgba(231, 236, 255, 0.32); + background: rgba(255, 255, 255, 0.08); +} + +.button-secondary:hover { + background: rgba(255, 255, 255, 0.16); +} + +.button-strong { + border-color: rgba(180, 214, 255, 0.95); + background: linear-gradient(180deg, rgba(110, 168, 255, 0.6), rgba(93, 154, 251, 0.42)); + box-shadow: 0 0 0 1px rgba(180, 214, 255, 0.24), 0 10px 24px rgba(72, 142, 255, 0.34); +} + +.icon-card, +.flow-detail, +.role-panel { + border-color: rgba(143, 190, 255, 0.22); + background: rgba(255, 255, 255, 0.045); +} + +.icon-card:hover { + transform: translateY(-2px); + border-color: rgba(143, 190, 255, 0.56); + background: rgba(255, 255, 255, 0.07); +} + +.icon-dot { + color: #f2f6ff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28); +} + +.metric-chip, +.landing-chip { + border-color: rgba(143, 190, 255, 0.45); + background: rgba(110, 168, 255, 0.18); + color: #f2f6ff; +} + +.permission-chip--allow { + border-color: rgba(123, 228, 149, 0.7); + background: rgba(123, 228, 149, 0.24); +} + +.permission-chip--deny { + border-color: rgba(255, 110, 138, 0.62); + background: rgba(255, 110, 138, 0.2); +} + +.flow-step, +.role-tabs__btn, +.segmented-tabs__btn, +.accordion__trigger { + border-color: rgba(143, 190, 255, 0.34); + color: #ebf1ff; +} + +.flow-step.is-active, +.role-tabs__btn.is-active, +.segmented-tabs__btn.is-active { + border-color: rgba(180, 214, 255, 0.95); + background: rgba(110, 168, 255, 0.34); + box-shadow: 0 0 0 1px rgba(180, 214, 255, 0.24) inset; +} + +.accordion__trigger:hover { + background: rgba(110, 168, 255, 0.14); } diff --git a/src/main/resources/static/app.js b/src/main/resources/static/app.js new file mode 100644 index 0000000..15de759 --- /dev/null +++ b/src/main/resources/static/app.js @@ -0,0 +1,253 @@ +(() => { + document.documentElement.classList.add("js-enhanced"); + + const landingRoot = document.querySelector(".landing-page"); + if (!landingRoot) { + return; + } + + const sectionIds = [ + "problem-solution", + "case-lifecycle", + "role-based-access", + "documents-reliability", + "audit-trust", + "architecture-quality", + ]; + + const navLinks = Array.from(document.querySelectorAll(".landing-nav-link")); + const sections = sectionIds + .map((id) => document.getElementById(id)) + .filter((section) => section !== null); + const progressBar = document.getElementById("landingProgressBar"); + const revealTargets = Array.from(document.querySelectorAll(".js-reveal")); + const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + + // Smooth scroll between module anchors for presentation flow. + navLinks.forEach((link) => { + link.addEventListener("click", (event) => { + const href = link.getAttribute("href"); + if (!href || !href.startsWith("#")) { + return; + } + const target = document.querySelector(href); + if (!target) { + return; + } + + event.preventDefault(); + target.scrollIntoView({ behavior: prefersReducedMotion ? "auto" : "smooth", block: "start" }); + history.replaceState(null, "", href); + }); + }); + + const updateActiveLink = (activeId) => { + navLinks.forEach((link) => { + const isActive = link.getAttribute("href") === `#${activeId}`; + link.classList.toggle("active", isActive); + if (isActive) { + link.setAttribute("aria-current", "true"); + } else { + link.removeAttribute("aria-current"); + } + }); + }; + + const sectionObserver = new IntersectionObserver( + (entries) => { + let topVisible = null; + for (const entry of entries) { + if (!entry.isIntersecting) { + continue; + } + if (!topVisible || entry.boundingClientRect.top < topVisible.boundingClientRect.top) { + topVisible = entry; + } + } + if (topVisible && topVisible.target.id) { + updateActiveLink(topVisible.target.id); + } + }, + { threshold: 0.45, rootMargin: "-10% 0px -45% 0px" } + ); + + sections.forEach((section) => sectionObserver.observe(section)); + + const updateProgress = () => { + if (!progressBar) { + return; + } + const scrollTop = window.scrollY || window.pageYOffset; + const documentHeight = document.documentElement.scrollHeight - window.innerHeight; + const progress = documentHeight <= 0 ? 100 : Math.min(100, Math.max(0, (scrollTop / documentHeight) * 100)); + progressBar.style.width = `${progress}%`; + }; + + window.addEventListener("scroll", updateProgress, { passive: true }); + window.addEventListener("resize", updateProgress); + updateProgress(); + + if (prefersReducedMotion) { + revealTargets.forEach((node) => node.classList.add("revealed")); + } else { + const revealObserver = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) { + return; + } + entry.target.classList.add("revealed"); + observer.unobserve(entry.target); + }); + }, + { threshold: 0.18 } + ); + + revealTargets.forEach((node) => revealObserver.observe(node)); + } + + const finalCta = document.getElementById("final-cta"); + const primaryCta = finalCta ? finalCta.querySelector(".button-strong") : null; + if (finalCta && primaryCta && !prefersReducedMotion) { + const ctaObserver = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) { + return; + } + primaryCta.classList.add("cta-attention"); + window.setTimeout(() => primaryCta.classList.remove("cta-attention"), 1200); + observer.unobserve(entry.target); + }); + }, + { threshold: 0.45 } + ); + ctaObserver.observe(finalCta); + } + + const tabButtons = Array.from(document.querySelectorAll(".segmented-tabs__btn")); + tabButtons.forEach((button) => { + button.addEventListener("click", () => { + const targetSelector = button.getAttribute("data-target"); + if (!targetSelector) return; + + tabButtons.forEach((btn) => { + const selected = btn === button; + btn.classList.toggle("is-active", selected); + btn.setAttribute("aria-selected", String(selected)); + }); + + document.querySelectorAll(".tab-panel").forEach((panel) => { + panel.hidden = true; + panel.classList.remove("is-active"); + }); + + const target = document.querySelector(targetSelector); + if (target) { + target.hidden = false; + target.classList.add("is-active"); + } + }); + }); + + const flowData = { + create: { + title: "🆕 Skapa ärende", + chips: ["🧑 Patientkontext", "🏁 Startstatus", "🧭 Tydlig start"], + }, + assign: { + title: "👤 Tilldela ansvar", + chips: ["👑 Ägare", "🛠️ Handläggare", "🎯 Klart ansvar"], + }, + update: { + title: "📝 Uppdatera och kommunicera", + chips: ["🗒️ Anteckningar", "📍 Status", "🤝 Samarbete"], + }, + close: { + title: "✅ Avsluta med spårbarhet", + chips: ["🏁 Avslut", "📚 Historik", "📈 Uppföljning"], + }, + }; + + const flowDetail = document.getElementById("flowDetail"); + const renderFlow = (key) => { + const item = flowData[key]; + if (!flowDetail || !item) return; + flowDetail.innerHTML = ` +

${item.title}

+
+ ${item.chips.map((chip) => `${chip}`).join("")} +
+ `; + }; + + const flowSteps = Array.from(document.querySelectorAll(".flow-step")); + flowSteps.forEach((button) => { + button.addEventListener("click", () => { + flowSteps.forEach((btn) => btn.classList.remove("is-active")); + button.classList.add("is-active"); + renderFlow(button.getAttribute("data-step")); + }); + }); + renderFlow("create"); + + const roleData = { + manager: { + allow: ["👀 Se alla ärenden", "🧩 Hantera tilldelning", "📜 Granska loggar"], + deny: ["🚫 Ingen patientimitation"], + }, + doctor: { + allow: ["✏️ Uppdatera tilldelade", "🗒️ Lägga anteckningar", "📁 Hantera dokument"], + deny: ["🚫 Ingen användaradministration"], + }, + nurse: { + allow: ["👀 Se handlagda ärenden", "🗒️ Lägga anteckningar"], + deny: ["🚫 Ingen full adminbehörighet"], + }, + patient: { + allow: ["📲 Följa egna ärenden", "💬 Lägga kommunikationsnotis"], + deny: ["🚫 Ingen åtkomst till andras ärenden"], + }, + }; + + const rolePanel = document.getElementById("rolePanel"); + const renderRole = (roleKey) => { + const role = roleData[roleKey]; + if (!role || !rolePanel) return; + rolePanel.innerHTML = ` +
+ ${role.allow.map((item) => `${item}`).join("")} + ${role.deny.map((item) => `${item}`).join("")} +
+ `; + }; + + const roleButtons = Array.from(document.querySelectorAll(".role-tabs__btn")); + roleButtons.forEach((button) => { + button.addEventListener("click", () => { + roleButtons.forEach((btn) => btn.classList.remove("is-active")); + button.classList.add("is-active"); + renderRole(button.getAttribute("data-role")); + }); + }); + renderRole("manager"); + + document.querySelectorAll("[data-accordion]").forEach((accordion) => { + const triggers = Array.from(accordion.querySelectorAll(".accordion__trigger")); + triggers.forEach((trigger) => { + trigger.addEventListener("click", () => { + const expanded = trigger.getAttribute("aria-expanded") === "true"; + triggers.forEach((btn) => { + btn.setAttribute("aria-expanded", "false"); + const panel = document.getElementById(btn.getAttribute("aria-controls") || ""); + if (panel) panel.hidden = true; + }); + if (!expanded) { + trigger.setAttribute("aria-expanded", "true"); + const panel = document.getElementById(trigger.getAttribute("aria-controls") || ""); + if (panel) panel.hidden = false; + } + }); + }); + }); +})(); diff --git a/src/main/resources/templates/landing.html b/src/main/resources/templates/landing.html new file mode 100644 index 0000000..5a2811b --- /dev/null +++ b/src/main/resources/templates/landing.html @@ -0,0 +1,148 @@ + + + + + +
+ + + +
+
+ 🎓 Projektpresentation + ⏱️ 5-7 minuter +
+

🏥 Vårdens ärenden i ett tryggt flöde

+

Ett samlat system för registrering, ansvar och uppföljning utan onödiga överlämningar.

+ +
+ + + +
+
+

🧭 Från splittrat till samlat

+

Två lägen: problem och lösning.

+
+
+ + +
+
+
+

Många verktyg

+

Tappar kontext

+

Långsamma överlämningar

+
+
+ +
+ +
+

🔄 Enkelt flöde, tydligt ansvar

+
    +
  1. +
  2. +
  3. +
  4. +
+
+
+ +
+

👥 Rätt åtkomst för rätt roll

+
+ + + + +
+
+
+ +
+
+

📄 Dokument som håller

+

Mindre text, mer överblick.

+
+
+ 🧪 MIME-kontroll + 🔁 Automatisk retry + 🛡️ Säker uppföljning +
+
+ + + + +
+
+ +
+
+

🕵️ Full spårbarhet

+

Vem, vad, när - direkt synligt.

+
+
+ 👤 Aktör + 📌 Händelse + ⏰ Tidpunkt +
+
+

🔎 Filter

Chef ser relevanta händelser snabbt.

+

📡 Live

Uppdateringar kan följas i nära realtid.

+

📊 Kontroll

Bättre underlag för uppföljning och kvalitet.

+
+
+ +
+

🧱 Byggt för stabil utveckling

+
+

🏗️ Tydliga lager

+

🔐 Säker standard

+

🧪 Automatiska tester

+
+
+ 🔍 Visa tekniska bevis +

MVC-struktur, servicevalidering och testade kärnflöden ger tryggare ändringar över tid.

+
+
+ +
+

🎬 Avslut

+

Redo att se helheten?

+

En plattform för säkrare samarbete och snabbare beslut i vården.

+
+ ⚡ Snabbare samordning + 🔒 Säkrare åtkomst + 🧾 Full spårbarhet +
+ +
+
+ + + diff --git a/start-local.ps1 b/start-local.ps1 index 12d8698..aca10f0 100644 --- a/start-local.ps1 +++ b/start-local.ps1 @@ -37,7 +37,7 @@ function Invoke-CommandWithMode { if ($LASTEXITCODE -ne 0) { Write-Host "Command failed in quiet mode (exit code: $LASTEXITCODE)." -ForegroundColor Red - Write-Host "Recent output from $tempLog:" -ForegroundColor DarkRed + Write-Host "Recent output from ${tempLog}:" -ForegroundColor DarkRed Get-Content -Path $tempLog -Tail 25 | ForEach-Object { Write-Host $_ -ForegroundColor DarkGray } } } From 752c785593bfe6fedb4f42bef9921d7f8f602db3 Mon Sep 17 00:00:00 2001 From: Linus Westling Date: Thu, 23 Apr 2026 16:38:16 +0200 Subject: [PATCH 2/4] seeded database --- src/main/resources/data.sql | 271 ++++++++++++++++++++++++++++++++---- 1 file changed, 244 insertions(+), 27 deletions(-) diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 4f099cb..c1e7c38 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,49 +1,266 @@ --- Seed Patients +-- ------------------------------------------------------------ +-- Core user accounts (shared seed) +-- ------------------------------------------------------------ +-- Uses GITHUB provider to avoid shipping known local credentials in shared data. +INSERT INTO user_account (id, email, password_hash, role, provider, provider_user_id, enabled, created_at) +VALUES ('120e8400-e29b-41d4-a716-446655440000', 'admin.seed@traumateam.local', NULL, 'MANAGER', 'GITHUB', 'seed-admin-001', true, CURRENT_TIMESTAMP - INTERVAL '45 days') +ON CONFLICT (email) DO NOTHING; + +INSERT INTO user_account (id, email, password_hash, role, provider, provider_user_id, enabled, created_at) +VALUES ('120e8400-e29b-41d4-a716-446655440001', 'doctor.seed@traumateam.local', NULL, 'DOCTOR', 'GITHUB', 'seed-doctor-001', true, CURRENT_TIMESTAMP - INTERVAL '42 days') +ON CONFLICT (email) DO NOTHING; + +INSERT INTO user_account (id, email, password_hash, role, provider, provider_user_id, enabled, created_at) +VALUES ('120e8400-e29b-41d4-a716-446655440002', 'patient.seed@traumateam.local', NULL, 'PATIENT', 'GITHUB', 'seed-patient-001', true, CURRENT_TIMESTAMP - INTERVAL '40 days') +ON CONFLICT (email) DO NOTHING; + +-- ------------------------------------------------------------ +-- Patients +-- ------------------------------------------------------------ +INSERT INTO patients (id, first_name, last_name, personal_identity_number, created_at) +VALUES ('550e8400-e29b-41d4-a716-446655440000', 'John', 'Doe', '19850512-1234', CURRENT_TIMESTAMP - INTERVAL '30 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO patients (id, first_name, last_name, personal_identity_number, created_at) +VALUES ('550e8400-e29b-41d4-a716-446655440001', 'Jane', 'Smith', '19920824-5678', CURRENT_TIMESTAMP - INTERVAL '28 days') +ON CONFLICT (id) DO NOTHING; + INSERT INTO patients (id, first_name, last_name, personal_identity_number, created_at) -VALUES ('550e8400-e29b-41d4-a716-446655440000', 'John', 'Doe', '19850512-1234', CURRENT_TIMESTAMP) +VALUES ('550e8400-e29b-41d4-a716-446655440002', 'Anders', 'Andersson', '19780315-9012', CURRENT_TIMESTAMP - INTERVAL '27 days') ON CONFLICT (id) DO NOTHING; INSERT INTO patients (id, first_name, last_name, personal_identity_number, created_at) -VALUES ('550e8400-e29b-41d4-a716-446655440001', 'Jane', 'Smith', '19920824-5678', CURRENT_TIMESTAMP) +VALUES ('550e8400-e29b-41d4-a716-446655440003', 'Maria', 'Nilsson', '19951201-7731', CURRENT_TIMESTAMP - INTERVAL '25 days') ON CONFLICT (id) DO NOTHING; INSERT INTO patients (id, first_name, last_name, personal_identity_number, created_at) -VALUES ('550e8400-e29b-41d4-a716-446655440002', 'Anders', 'Andersson', '19780315-9012', CURRENT_TIMESTAMP) +VALUES ('550e8400-e29b-41d4-a716-446655440004', 'Erik', 'Larsson', '19691109-1942', CURRENT_TIMESTAMP - INTERVAL '24 days') ON CONFLICT (id) DO NOTHING; --- Seed Employees -INSERT INTO employees (id, display_name, role, created_at) -VALUES ('770e8400-e29b-41d4-a716-446655440000', 'Admin User', 'MANAGER', CURRENT_TIMESTAMP) +INSERT INTO patients (id, first_name, last_name, personal_identity_number, created_at) +VALUES ('550e8400-e29b-41d4-a716-446655440005', 'Sofia', 'Berg', '20010218-3307', CURRENT_TIMESTAMP - INTERVAL '20 days') +ON CONFLICT (id) DO NOTHING; + +-- ------------------------------------------------------------ +-- Employees +-- ------------------------------------------------------------ +INSERT INTO employees (id, display_name, github_username, role, created_at) +VALUES ('770e8400-e29b-41d4-a716-446655440000', 'Admin User', 'seed-admin', 'MANAGER', CURRENT_TIMESTAMP - INTERVAL '45 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO employees (id, display_name, github_username, role, created_at) +VALUES ('770e8400-e29b-41d4-a716-446655440001', 'Dr. Alice Roberts', 'alice-roberts-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '42 days') ON CONFLICT (id) DO NOTHING; -INSERT INTO employees (id, display_name, role, created_at) -VALUES ('770e8400-e29b-41d4-a716-446655440001', 'Dr. Alice Roberts', 'DOCTOR', CURRENT_TIMESTAMP) +INSERT INTO employees (id, display_name, github_username, role, created_at) +VALUES ('770e8400-e29b-41d4-a716-446655440002', 'Nurse Bob Jones', 'bob-jones-rn', 'NURSE', CURRENT_TIMESTAMP - INTERVAL '39 days') ON CONFLICT (id) DO NOTHING; -INSERT INTO employees (id, display_name, role, created_at) -VALUES ('770e8400-e29b-41d4-a716-446655440002', 'Nurse Bob Jones', 'NURSE', CURRENT_TIMESTAMP) +INSERT INTO employees (id, display_name, github_username, role, created_at) +VALUES ('770e8400-e29b-41d4-a716-446655440003', 'Dr. Sara Lindholm', 'sara-lindholm-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '35 days') ON CONFLICT (id) DO NOTHING; --- Seed Cases +INSERT INTO employees (id, display_name, github_username, role, created_at) +VALUES ('770e8400-e29b-41d4-a716-446655440004', 'Nurse Emma Karlsson', 'emma-karlsson-rn', 'NURSE', CURRENT_TIMESTAMP - INTERVAL '33 days') +ON CONFLICT (id) DO NOTHING; + +-- ------------------------------------------------------------ +-- Cases +-- ------------------------------------------------------------ INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) -VALUES ('990e8400-e29b-41d4-a716-446655440000', 'Acute Chest Pain', 'Patient arrived with severe chest pain and shortness of breath.', 'ASSIGNED', '550e8400-e29b-41d4-a716-446655440000', '770e8400-e29b-41d4-a716-446655440001', '770e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP) +VALUES ('990e8400-e29b-41d4-a716-446655440000', 'Acute Chest Pain', + 'Patient arrived with severe chest pain and shortness of breath during exercise.', + 'COMMUNICATION', '550e8400-e29b-41d4-a716-446655440000', '770e8400-e29b-41d4-a716-446655440001', + '770e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP - INTERVAL '12 days') ON CONFLICT (id) DO NOTHING; INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) -VALUES ('990e8400-e29b-41d4-a716-446655440001', 'Follow-up: Fracture', 'Routine follow-up for a healed radial fracture.', 'ASSIGNED', '550e8400-e29b-41d4-a716-446655440001', '770e8400-e29b-41d4-a716-446655440001', '770e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP) +VALUES ('990e8400-e29b-41d4-a716-446655440001', 'Follow-up: Fracture', + 'Routine follow-up after distal radius fracture. Verify mobility and pain level.', + 'ASSIGNED', '550e8400-e29b-41d4-a716-446655440001', '770e8400-e29b-41d4-a716-446655440001', + '770e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP - INTERVAL '11 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) +VALUES ('990e8400-e29b-41d4-a716-446655440002', 'Persistent Migraine', + 'Recurring migraine episodes for two weeks with light sensitivity and nausea.', + 'UPDATED', '550e8400-e29b-41d4-a716-446655440002', '770e8400-e29b-41d4-a716-446655440003', + '770e8400-e29b-41d4-a716-446655440004', CURRENT_TIMESTAMP - INTERVAL '10 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) +VALUES ('990e8400-e29b-41d4-a716-446655440003', 'Post-surgery Wound Check', + 'Follow-up wound inspection after appendectomy; monitor healing and infection markers.', + 'CLOSED', '550e8400-e29b-41d4-a716-446655440003', '770e8400-e29b-41d4-a716-446655440001', + '770e8400-e29b-41d4-a716-446655440004', CURRENT_TIMESTAMP - INTERVAL '9 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) +VALUES ('990e8400-e29b-41d4-a716-446655440004', 'Hypertension Medication Review', + 'Blood pressure fluctuations despite current medication. Evaluate treatment adjustment.', + 'COMMUNICATION', '550e8400-e29b-41d4-a716-446655440004', '770e8400-e29b-41d4-a716-446655440003', + '770e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP - INTERVAL '8 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) +VALUES ('990e8400-e29b-41d4-a716-446655440005', 'Allergic Reaction Observation', + 'Mild allergic reaction after new antibiotic; monitor symptoms and response to treatment.', + 'CREATED', '550e8400-e29b-41d4-a716-446655440005', '770e8400-e29b-41d4-a716-446655440001', + '770e8400-e29b-41d4-a716-446655440004', CURRENT_TIMESTAMP - INTERVAL '3 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) +VALUES ('990e8400-e29b-41d4-a716-446655440006', 'Knee Pain After Sports Injury', + 'Suspected meniscus strain after football practice; reduced range of motion.', + 'ASSIGNED', '550e8400-e29b-41d4-a716-446655440001', '770e8400-e29b-41d4-a716-446655440003', + '770e8400-e29b-41d4-a716-446655440004', CURRENT_TIMESTAMP - INTERVAL '2 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO cases (id, title, description, status, patient_id, owner_id, handler_id, created_at) +VALUES ('990e8400-e29b-41d4-a716-446655440007', 'Diabetes Checkup and Education', + 'Quarterly diabetes review with glucose trends, lifestyle coaching and medication adherence.', + 'UPDATED', '550e8400-e29b-41d4-a716-446655440000', '770e8400-e29b-41d4-a716-446655440001', + '770e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP - INTERVAL '1 days') +ON CONFLICT (id) DO NOTHING; + +-- ------------------------------------------------------------ +-- Case notes (comments) +-- ------------------------------------------------------------ +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440000', '990e8400-e29b-41d4-a716-446655440000', + 'ECG completed. Awaiting blood panel for troponin and CRP.', + 'Dr. Alice Roberts', 'alice-roberts-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '12 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440001', '990e8400-e29b-41d4-a716-446655440000', + 'Patient stable on observation ward. Chest pain now rated 3/10.', + 'Nurse Bob Jones', 'bob-jones-rn', 'NURSE', CURRENT_TIMESTAMP - INTERVAL '11 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440002', '990e8400-e29b-41d4-a716-446655440001', + 'Cast removed. Physiotherapy exercises explained and demonstrated.', + 'Dr. Alice Roberts', 'alice-roberts-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '10 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440003', '990e8400-e29b-41d4-a716-446655440002', + 'Triggered by stress and lack of sleep. Recommended headache diary.', + 'Dr. Sara Lindholm', 'sara-lindholm-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '9 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440004', '990e8400-e29b-41d4-a716-446655440003', + 'Wound dry and clean. Sutures removed. No signs of infection.', + 'Nurse Emma Karlsson', 'emma-karlsson-rn', 'NURSE', CURRENT_TIMESTAMP - INTERVAL '8 days') ON CONFLICT (id) DO NOTHING; --- Creates the new table for all user accounts (both local and GitHub) -CREATE TABLE IF NOT EXISTS user_account ( - id UUID PRIMARY KEY, - email VARCHAR(255) UNIQUE, - password_hash VARCHAR(255), - role VARCHAR(50) NOT NULL, - provider VARCHAR(50) NOT NULL, - provider_user_id VARCHAR(255), - enabled BOOLEAN NOT NULL, - created_at TIMESTAMP WITH TIME ZONE NOT NULL - ); +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440005', '990e8400-e29b-41d4-a716-446655440004', + 'Home blood-pressure log reviewed. Evening values remain elevated.', + 'Dr. Sara Lindholm', 'sara-lindholm-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '7 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440006', '990e8400-e29b-41d4-a716-446655440006', + 'Swelling reduced. MRI referral submitted for meniscus evaluation.', + 'Dr. Sara Lindholm', 'sara-lindholm-md', 'DOCTOR', CURRENT_TIMESTAMP - INTERVAL '1 days') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO case_notes (id, case_id, content, author_display_name, author_github_username, author_role, created_at) +VALUES ('a90e8400-e29b-41d4-a716-446655440007', '990e8400-e29b-41d4-a716-446655440007', + 'Diet plan and glucose monitoring schedule reviewed with patient.', + 'Nurse Bob Jones', 'bob-jones-rn', 'NURSE', CURRENT_TIMESTAMP - INTERVAL '20 hours') +ON CONFLICT (id) DO NOTHING; + +-- ------------------------------------------------------------ +-- Documents +-- ------------------------------------------------------------ +INSERT INTO documents (id, file_name, s3_key, content_type, file_size, uploaded_at, uploaded_by, case_id) +VALUES ('b90e8400-e29b-41d4-a716-446655440000', 'ecg-initial.pdf', 'cases/990e8400-e29b-41d4-a716-446655440000/ecg-initial.pdf', + 'application/pdf', 238412, CURRENT_TIMESTAMP - INTERVAL '12 days', '770e8400-e29b-41d4-a716-446655440001', + '990e8400-e29b-41d4-a716-446655440000') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO documents (id, file_name, s3_key, content_type, file_size, uploaded_at, uploaded_by, case_id) +VALUES ('b90e8400-e29b-41d4-a716-446655440001', 'xray-fracture-followup.png', 'cases/990e8400-e29b-41d4-a716-446655440001/xray-fracture-followup.png', + 'image/png', 484190, CURRENT_TIMESTAMP - INTERVAL '10 days', '770e8400-e29b-41d4-a716-446655440002', + '990e8400-e29b-41d4-a716-446655440001') +ON CONFLICT (id) DO NOTHING; --- Local admin seed intentionally moved to data-local.sql so production/shared seeds --- do not embed a known credential hash. +INSERT INTO documents (id, file_name, s3_key, content_type, file_size, uploaded_at, uploaded_by, case_id) +VALUES ('b90e8400-e29b-41d4-a716-446655440002', 'migraine-diary-week1.docx', 'cases/990e8400-e29b-41d4-a716-446655440002/migraine-diary-week1.docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 91324, + CURRENT_TIMESTAMP - INTERVAL '8 days', '770e8400-e29b-41d4-a716-446655440004', '990e8400-e29b-41d4-a716-446655440002') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO documents (id, file_name, s3_key, content_type, file_size, uploaded_at, uploaded_by, case_id) +VALUES ('b90e8400-e29b-41d4-a716-446655440003', 'post-op-wound-photo.jpg', 'cases/990e8400-e29b-41d4-a716-446655440003/post-op-wound-photo.jpg', + 'image/jpeg', 356778, CURRENT_TIMESTAMP - INTERVAL '9 days', '770e8400-e29b-41d4-a716-446655440004', + '990e8400-e29b-41d4-a716-446655440003') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO documents (id, file_name, s3_key, content_type, file_size, uploaded_at, uploaded_by, case_id) +VALUES ('b90e8400-e29b-41d4-a716-446655440004', 'bp-home-readings.xlsx', 'cases/990e8400-e29b-41d4-a716-446655440004/bp-home-readings.xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 120512, + CURRENT_TIMESTAMP - INTERVAL '6 days', '770e8400-e29b-41d4-a716-446655440002', '990e8400-e29b-41d4-a716-446655440004') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO documents (id, file_name, s3_key, content_type, file_size, uploaded_at, uploaded_by, case_id) +VALUES ('b90e8400-e29b-41d4-a716-446655440005', 'mri-referral.pdf', 'cases/990e8400-e29b-41d4-a716-446655440006/mri-referral.pdf', + 'application/pdf', 204889, CURRENT_TIMESTAMP - INTERVAL '22 hours', '770e8400-e29b-41d4-a716-446655440003', + '990e8400-e29b-41d4-a716-446655440006') +ON CONFLICT (id) DO NOTHING; + +-- ------------------------------------------------------------ +-- Audit events +-- ------------------------------------------------------------ +INSERT INTO audit_events (id, occurred_at, actor_id, actor_role, principal_name, request_path, query_string, handler, + response_status, error_type, case_id, status_change, event_name, description, client_ip, user_agent) +VALUES ('c90e8400-e29b-41d4-a716-446655440000', CURRENT_TIMESTAMP - INTERVAL '12 days', + '770e8400-e29b-41d4-a716-446655440001', 'DOCTOR', 'doctor.seed@traumateam.local', + '/ui/cases/990e8400-e29b-41d4-a716-446655440000', NULL, 'CaseUiController#showCase', + 200, NULL, '990e8400-e29b-41d4-a716-446655440000', 'CREATED->ASSIGNED', 'CASE_STATUS_CHANGED', + 'Case owner assigned to Dr. Alice Roberts.', '127.0.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO audit_events (id, occurred_at, actor_id, actor_role, principal_name, request_path, query_string, handler, + response_status, error_type, case_id, status_change, event_name, description, client_ip, user_agent) +VALUES ('c90e8400-e29b-41d4-a716-446655440001', CURRENT_TIMESTAMP - INTERVAL '11 days', + '770e8400-e29b-41d4-a716-446655440002', 'NURSE', 'nurse.bob@traumateam.local', + '/ui/cases/990e8400-e29b-41d4-a716-446655440000/comments', NULL, 'CaseNoteUiController#create', + 302, NULL, '990e8400-e29b-41d4-a716-446655440000', NULL, 'CASE_NOTE_ADDED', + 'Nurse added observation note after first medication cycle.', '127.0.0.1', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO audit_events (id, occurred_at, actor_id, actor_role, principal_name, request_path, query_string, handler, + response_status, error_type, case_id, status_change, event_name, description, client_ip, user_agent) +VALUES ('c90e8400-e29b-41d4-a716-446655440002', CURRENT_TIMESTAMP - INTERVAL '10 days', + '770e8400-e29b-41d4-a716-446655440001', 'DOCTOR', 'doctor.seed@traumateam.local', + '/ui/cases/990e8400-e29b-41d4-a716-446655440001/documents', NULL, 'DocumentUiController#upload', + 201, NULL, '990e8400-e29b-41d4-a716-446655440001', NULL, 'DOCUMENT_UPLOADED', + 'Follow-up x-ray uploaded and attached to case timeline.', '127.0.0.1', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO audit_events (id, occurred_at, actor_id, actor_role, principal_name, request_path, query_string, handler, + response_status, error_type, case_id, status_change, event_name, description, client_ip, user_agent) +VALUES ('c90e8400-e29b-41d4-a716-446655440003', CURRENT_TIMESTAMP - INTERVAL '8 days', + '770e8400-e29b-41d4-a716-446655440003', 'DOCTOR', 'doctor.sara@traumateam.local', + '/api/cases/990e8400-e29b-41d4-a716-446655440004/status', 'target=COMMUNICATION', 'CaseController#updateStatus', + 200, NULL, '990e8400-e29b-41d4-a716-446655440004', 'ASSIGNED->COMMUNICATION', 'CASE_STATUS_CHANGED', + 'Case moved to communication after medication discussion with patient.', '127.0.0.1', + 'PostmanRuntime/7.43.0') +ON CONFLICT (id) DO NOTHING; + +INSERT INTO audit_events (id, occurred_at, actor_id, actor_role, principal_name, request_path, query_string, handler, + response_status, error_type, case_id, status_change, event_name, description, client_ip, user_agent) +VALUES ('c90e8400-e29b-41d4-a716-446655440004', CURRENT_TIMESTAMP - INTERVAL '2 days', + '770e8400-e29b-41d4-a716-446655440000', 'MANAGER', 'admin.seed@traumateam.local', + '/ui/admin/audit', NULL, 'AdminAuditUiController#index', + 200, NULL, NULL, NULL, 'AUDIT_VIEWED', + 'Manager reviewed audit overview dashboard.', '127.0.0.1', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)') +ON CONFLICT (id) DO NOTHING; From 95ae0ab06e62dec204a9167c7926f67466146024 Mon Sep 17 00:00:00 2001 From: Linus Westling Date: Thu, 23 Apr 2026 16:41:07 +0200 Subject: [PATCH 3/4] u sure? --- .../infrastructure/persistence/UserAccountEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/example/projektarendehantering/infrastructure/persistence/UserAccountEntity.java b/src/main/java/org/example/projektarendehantering/infrastructure/persistence/UserAccountEntity.java index 32506f7..8e6268c 100644 --- a/src/main/java/org/example/projektarendehantering/infrastructure/persistence/UserAccountEntity.java +++ b/src/main/java/org/example/projektarendehantering/infrastructure/persistence/UserAccountEntity.java @@ -49,3 +49,4 @@ public void onPrePersist() { } } } + From ca68c4e0fcaa255e17dbf1e6beac3db685bf5f1c Mon Sep 17 00:00:00 2001 From: mattknatt Date: Thu, 23 Apr 2026 16:45:41 +0200 Subject: [PATCH 4/4] test --- src/main/resources/data.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index c1e7c38..734607f 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,5 +1,6 @@ -- ------------------------------------------------------------ -- Core user accounts (shared seed) +-- -- ------------------------------------------------------------ -- Uses GITHUB provider to avoid shipping known local credentials in shared data. INSERT INTO user_account (id, email, password_hash, role, provider, provider_user_id, enabled, created_at)