diff --git a/app/modules/sag/frontend/views.py b/app/modules/sag/frontend/views.py index 0d60a40..0eba19d 100644 --- a/app/modules/sag/frontend/views.py +++ b/app/modules/sag/frontend/views.py @@ -3,7 +3,7 @@ import json from datetime import date, datetime from typing import Optional from fastapi import APIRouter, HTTPException, Query, Request -from fastapi.responses import HTMLResponse +from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from pathlib import Path from app.core.database import execute_query @@ -455,6 +455,9 @@ async def sag_varekob_salg(request: Request): @router.get("/sag/{sag_id}", response_class=HTMLResponse) async def sag_detaljer(request: Request, sag_id: int): + """Redirect legacy case details URL to v3.""" + return RedirectResponse(url=f"/sag/{sag_id}/v3", status_code=307) + """Display case details.""" try: # Fetch main case diff --git a/app/modules/sag/templates/detail.html b/app/modules/sag/templates/detail.html index 4e44f8d..bbeeaba 100644 --- a/app/modules/sag/templates/detail.html +++ b/app/modules/sag/templates/detail.html @@ -2097,6 +2097,23 @@ box-shadow: 0 4px 12px rgba(15, 76, 117, 0.10); } + #caseTabsContent .tab-pane:not(#details) .card, + #caseTabsContent .tab-pane:not(#details) .history-timeline-shell { + --module-accent: var(--accent); + border: 2px solid rgba(15, 76, 117, 0.28) !important; + border-left: 2px solid rgba(15, 76, 117, 0.28) !important; + border-radius: 12px !important; + box-shadow: 0 4px 12px rgba(15, 76, 117, 0.10) !important; + background: linear-gradient(165deg, color-mix(in srgb, var(--module-accent, var(--accent)) 6%, var(--bg-card)) 0%, var(--bg-card) 100%); + overflow: hidden; + } + + #caseTabsContent .tab-pane:not(#details) .card .card-header, + #caseTabsContent .tab-pane:not(#details) .history-timeline-toolbar { + border-bottom: 1px solid color-mix(in srgb, var(--module-accent, var(--accent)) 22%, #d1d5db); + background: color-mix(in srgb, var(--module-accent, var(--accent)) 7%, var(--bg-card)); + } + .left-module-card, .right-module-card { border: 2px solid rgba(15, 76, 117, 0.28) !important; @@ -2110,6 +2127,20 @@ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28); } + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .card, + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .history-timeline-shell { + border: 2px solid rgba(117, 167, 204, 0.45) !important; + border-left: 2px solid rgba(117, 167, 204, 0.45) !important; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28) !important; + background: linear-gradient(165deg, color-mix(in srgb, var(--module-accent, #69a6d5) 12%, rgba(18, 28, 40, 0.94)) 0%, rgba(18, 28, 40, 0.94) 100%); + } + + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .card .card-header, + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .history-timeline-toolbar { + border-bottom-color: color-mix(in srgb, var(--module-accent, #69a6d5) 45%, #4b5563); + background: color-mix(in srgb, var(--module-accent, #69a6d5) 18%, rgba(18, 28, 40, 0.98)); + } + [data-bs-theme="dark"] .left-module-card, [data-bs-theme="dark"] .right-module-card { border: 2px solid rgba(117, 167, 204, 0.45) !important; @@ -9336,9 +9367,11 @@ if (!res.ok) throw new Error('Kunne ikke hente tidsforbrug'); const entries = await res.json(); timeV1EntriesById = Object.fromEntries((entries || []).map((entry) => [Number(entry.id), entry])); + window.initialCaseTabCounts = Object.assign({}, window.initialCaseTabCounts || {}, { timetracking: (entries || []).length }); renderTimeV1Timeline(entries || []); renderTimeV1Summary(entries || []); setModuleContentState('timetracking', (entries || []).length > 0); + if (typeof updateCaseTabCountBadges === 'function') updateCaseTabCountBadges(); } catch (error) { console.error(error); const timeline = document.getElementById('timeTimelineColumns'); @@ -10700,6 +10733,9 @@ let caseAddPanelInitialized = false; let caseAddActiveAction = null; let caseAddOriginalShowRelModal = null; + window.initialCaseTabCounts = Object.assign({}, window.initialCaseTabCounts || {}, { + timetracking: {{ (time_entries or [])|length }} + }); const CASE_ADD_ACTIONS = [ { action: 'time', label: 'Tidregistrering', icon: 'bi-clock', moduleKey: 'time', relFn: 'openRelTimeModal' }, { action: 'note', label: 'Kommentar', icon: 'bi-chat-left-text', moduleKey: 'solution', relFn: 'openRelNoteModal' }, @@ -12239,9 +12275,10 @@ const timeEntriesStore = (typeof timeV1EntriesById !== 'undefined' && timeV1EntriesById && typeof timeV1EntriesById === 'object') ? timeV1EntriesById : null; - const timeCount = timeEntriesStore - ? Object.keys(timeEntriesStore).length - : document.querySelectorAll('#timetracking tbody tr').length; + const initialTimeCount = Number(window.initialCaseTabCounts?.timetracking || 0); + const loadedTimeCount = timeEntriesStore ? Object.keys(timeEntriesStore).length : 0; + const timeDomCount = document.querySelectorAll('#timetracking tbody tr').length; + const timeCount = Math.max(initialTimeCount, loadedTimeCount, timeDomCount); _setCaseTabCountBadge('timetrackingTabCountBadge', timeCount); const subscriptionCount = _countRows('#subscriptionItemsBody'); diff --git a/app/modules/sag/templates/detail_v3.html b/app/modules/sag/templates/detail_v3.html index 9c1ec1b..01f4a92 100644 --- a/app/modules/sag/templates/detail_v3.html +++ b/app/modules/sag/templates/detail_v3.html @@ -2021,6 +2021,108 @@ color: #6c757d; } + .contact-actions { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 0.25rem; + width: 100%; + } + + .contact-actions .btn { + width: 1.65rem; + height: 1.65rem; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + } + + .contact-info-hero { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + border: 1px solid rgba(15, 76, 117, 0.18); + border-radius: 0.75rem; + background: linear-gradient(145deg, rgba(15, 76, 117, 0.08), rgba(15, 76, 117, 0.02)); + margin-bottom: 0.9rem; + } + + .contact-info-avatar { + width: 2.4rem; + height: 2.4rem; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.8rem; + font-weight: 700; + color: #fff; + background: var(--accent); + box-shadow: 0 4px 12px rgba(15, 76, 117, 0.24); + flex-shrink: 0; + } + + .contact-info-title { + font-weight: 700; + margin: 0; + line-height: 1.2; + } + + .contact-info-subtitle { + color: #6c757d; + font-size: 0.82rem; + margin-top: 0.1rem; + } + + .contact-quick-actions { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; + margin-bottom: 0.9rem; + } + + .contact-quick-actions .btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.35rem; + font-size: 0.8rem; + padding: 0.45rem 0.4rem; + } + + .contact-info-grid { + display: grid; + grid-template-columns: 1fr; + gap: 0.45rem; + } + + .contact-info-row { + display: flex; + justify-content: space-between; + gap: 0.8rem; + padding: 0.42rem 0.1rem; + border-bottom: 1px dashed rgba(0, 0, 0, 0.08); + } + + .contact-info-row:last-child { + border-bottom: none; + } + + .contact-info-label { + color: #6c757d; + font-size: 0.78rem; + min-width: 90px; + } + + .contact-info-value { + text-align: right; + font-size: 0.86rem; + word-break: break-word; + } + .hardware-list-header, .hardware-row, .location-list-header, @@ -2354,6 +2456,23 @@ box-shadow: 0 4px 12px rgba(15, 76, 117, 0.10); } + #caseTabsContent .tab-pane:not(#details) .card, + #caseTabsContent .tab-pane:not(#details) .history-timeline-shell { + --module-accent: var(--accent); + border: 2px solid rgba(15, 76, 117, 0.28) !important; + border-left: 2px solid rgba(15, 76, 117, 0.28) !important; + border-radius: 12px !important; + box-shadow: 0 4px 12px rgba(15, 76, 117, 0.10) !important; + background: linear-gradient(165deg, color-mix(in srgb, var(--module-accent, var(--accent)) 6%, var(--bg-card)) 0%, var(--bg-card) 100%); + overflow: hidden; + } + + #caseTabsContent .tab-pane:not(#details) .card .card-header, + #caseTabsContent .tab-pane:not(#details) .history-timeline-toolbar { + border-bottom: 1px solid color-mix(in srgb, var(--module-accent, var(--accent)) 22%, #d1d5db); + background: color-mix(in srgb, var(--module-accent, var(--accent)) 7%, var(--bg-card)); + } + .left-module-card, .right-module-card { border: 2px solid rgba(15, 76, 117, 0.28) !important; @@ -2367,6 +2486,20 @@ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28); } + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .card, + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .history-timeline-shell { + border: 2px solid rgba(117, 167, 204, 0.45) !important; + border-left: 2px solid rgba(117, 167, 204, 0.45) !important; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28) !important; + background: linear-gradient(165deg, color-mix(in srgb, var(--module-accent, #69a6d5) 12%, rgba(18, 28, 40, 0.94)) 0%, rgba(18, 28, 40, 0.94) 100%); + } + + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .card .card-header, + [data-bs-theme="dark"] #caseTabsContent .tab-pane:not(#details) .history-timeline-toolbar { + border-bottom-color: color-mix(in srgb, var(--module-accent, #69a6d5) 45%, #4b5563); + background: color-mix(in srgb, var(--module-accent, #69a6d5) 18%, rgba(18, 28, 40, 0.98)); + } + [data-bs-theme="dark"] .left-module-card, [data-bs-theme="dark"] .right-module-card { border: 2px solid rgba(117, 167, 204, 0.45) !important; @@ -3288,7 +3421,7 @@