hotfix: server-render initial telephony calls

This commit is contained in:
Christian 2026-05-04 22:46:31 +02:00
parent 93da2866dc
commit b1a4342a9a
4 changed files with 106 additions and 3 deletions

15
RELEASE_NOTES_v2.2.85.md Normal file
View File

@ -0,0 +1,15 @@
# Release Notes v2.2.85
Dato: 2026-05-04
## Hotfix
- Telefoni-siden (`/telefoni`) rendrer nu seneste opkald server-side ved page load (SSR fallback).
- Dette sikrer, at brugeren ser opkald med det samme, selv hvis browserens JS/rendering/filter-state fejler eller er cachet.
- Klient-side `loadCalls()` koerer stadig bagefter og opdaterer tabellen som foer.
## Berorte filer
- `app/modules/telefoni/frontend/views.py`
- `app/modules/telefoni/templates/log.html`
- `VERSION`

View File

@ -1 +1 @@
2.2.84 2.2.85

View File

@ -3,6 +3,7 @@ import logging
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from app.core.database import execute_query
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
@ -11,4 +12,43 @@ templates = Jinja2Templates(directory="app")
@router.get("/telefoni", response_class=HTMLResponse) @router.get("/telefoni", response_class=HTMLResponse)
async def telefoni_log_page(request: Request): async def telefoni_log_page(request: Request):
return templates.TemplateResponse("modules/telefoni/templates/log.html", {"request": request}) initial_calls = []
try:
initial_calls = execute_query(
"""
SELECT
t.id,
t.direction,
COALESCE(
NULLIF(TRIM(t.ekstern_nummer), ''),
NULLIF(TRIM(t.raw_payload->>'caller'), ''),
NULLIF(TRIM(t.raw_payload->>'callee'), '')
) AS display_number,
t.started_at,
t.duration_sec,
t.ended_at,
u.full_name,
u.username,
t.kontakt_id,
CONCAT(COALESCE(c.first_name, ''), ' ', COALESCE(c.last_name, '')) AS contact_name,
t.sag_id,
s.titel AS sag_titel
FROM telefoni_opkald t
LEFT JOIN users u ON u.user_id = t.bruger_id
LEFT JOIN contacts c ON c.id = t.kontakt_id
LEFT JOIN sag_sager s ON s.id = t.sag_id
ORDER BY t.started_at DESC
LIMIT 50
""",
(),
) or []
except Exception as e:
logger.warning("⚠️ Could not load initial telefoni calls for SSR fallback: %s", e)
return templates.TemplateResponse(
"modules/telefoni/templates/log.html",
{
"request": request,
"initial_calls": initial_calls,
},
)

View File

@ -59,7 +59,41 @@
</tr> </tr>
</thead> </thead>
<tbody id="telefoniRows"> <tbody id="telefoniRows">
{% if initial_calls and initial_calls|length > 0 %}
{% for r in initial_calls %}
<tr>
<td>{{ r.started_at or '-' }}</td>
<td>{{ r.full_name or r.username or '-' }}</td>
<td>{{ 'Udgående' if r.direction == 'outbound' else 'Indgående' }}</td>
<td>{{ r.display_number or '-' }}</td>
<td>
{% if r.kontakt_id %}
<a href="/contacts/{{ r.kontakt_id }}">{{ r.contact_name or ('Kontakt #' ~ r.kontakt_id) }}</a>
{% else %}
-
{% endif %}
</td>
<td>
{% if r.sag_id %}
<a href="/sag/{{ r.sag_id }}/v3">{{ r.sag_titel or ('Sag #' ~ r.sag_id) }}</a>
{% else %}
-
{% endif %}
</td>
<td class="text-end">
{% if r.duration_sec is not none %}
{{ r.duration_sec }}s
{% elif r.ended_at %}
-
{% else %}
I gang
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="7" class="text-muted small">Indlæser...</td></tr> <tr><td colspan="7" class="text-muted small">Indlæser...</td></tr>
{% endif %}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -178,6 +212,7 @@ function escapeHtml(str) {
} }
let telefoniCurrentUserId = null; let telefoniCurrentUserId = null;
let telefoniAutoResetTried = false;
const telefoniCallMap = new Map(); const telefoniCallMap = new Map();
const linkSagState = { const linkSagState = {
callId: null, callId: null,
@ -846,10 +881,22 @@ async function loadCalls() {
telefoniCallMap.clear(); telefoniCallMap.clear();
(rows || []).forEach(r => telefoniCallMap.set(Number(r.id), r)); (rows || []).forEach(r => telefoniCallMap.set(Number(r.id), r));
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
const hadFilters = Boolean(userId || from || to || withoutCase);
if (hadFilters && !telefoniAutoResetTried) {
telefoniAutoResetTried = true;
document.getElementById('filterUser').value = '';
document.getElementById('filterFrom').value = '';
document.getElementById('filterTo').value = '';
document.getElementById('filterWithoutCase').checked = false;
await loadCalls();
return;
}
tbody.innerHTML = '<tr><td colspan="7" class="text-muted small">Ingen opkald fundet</td></tr>'; tbody.innerHTML = '<tr><td colspan="7" class="text-muted small">Ingen opkald fundet</td></tr>';
return; return;
} }
telefoniAutoResetTried = false;
tbody.innerHTML = rows.map(r => { tbody.innerHTML = rows.map(r => {
const started = r.started_at ? new Date(r.started_at) : null; const started = r.started_at ? new Date(r.started_at) : null;
const dateTxt = started ? started.toLocaleString('da-DK') : '-'; const dateTxt = started ? started.toLocaleString('da-DK') : '-';
@ -974,6 +1021,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (fromFilter) fromFilter.value = ''; if (fromFilter) fromFilter.value = '';
if (toFilter) toFilter.value = ''; if (toFilter) toFilter.value = '';
if (withoutCaseFilter) withoutCaseFilter.checked = false; if (withoutCaseFilter) withoutCaseFilter.checked = false;
telefoniAutoResetTried = false;
await loadUsers(); await loadUsers();
document.getElementById('btnRefresh').addEventListener('click', loadCalls); document.getElementById('btnRefresh').addEventListener('click', loadCalls);