(function () { let latestSections = {}; let latestContextActions = { global: [], context: [] }; let activeKey = 'timer'; let overviewFilter = null; let ws = null; let pollTimer = null; let wsReconnectTimer = null; let latestNotificationCount = 0; let latestNotifications = []; function byId(id) { return document.getElementById(id); } function escapeHtml(value) { return String(value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } async function fetchBottomBarState() { const contextPath = encodeURIComponent(window.location.pathname || '/'); const response = await fetch('/api/v1/bottom-bar/state?context=' + contextPath, { credentials: 'same-origin', headers: { 'Accept': 'application/json' } }); if (!response.ok) { throw new Error('Could not load bottom bar state'); } return response.json(); } function applyState(data) { if (data && data.enabled) { latestSections = data.sections || {}; latestContextActions = (latestSections.context_actions || { global: [], context: [] }); latestNotificationCount = Number((((data || {}).notifications || {}).count) || 0); latestNotifications = (((data || {}).notifications || {}).items || []); syncBossTabVisibility(); updateBar(latestSections); updateActivityZone(); renderTabPanel(); setVisibility(true); return; } setVisibility(false); } function setVisibility(enabled) { const shell = byId('globalBottomBar'); if (!shell) { return; } if (enabled) { shell.hidden = false; window.requestAnimationFrame(function () { shell.classList.add('is-visible'); }); } else { shell.classList.remove('is-visible'); window.setTimeout(function () { if (!shell.classList.contains('is-visible')) { shell.hidden = true; } }, 320); } document.body.classList.toggle('bottom-bar-visible', !!enabled); if (!enabled) { document.body.classList.remove('bottom-bar-expanded'); } } function setExpanded(expanded) { const shell = byId('globalBottomBar'); const toggle = byId('bbSheetToggle'); const panel = byId('bbSheetPanel'); if (!shell || !toggle || !panel) { return; } shell.classList.toggle('is-expanded', !!expanded); document.body.classList.toggle('bottom-bar-expanded', !!expanded); toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false'); panel.setAttribute('aria-hidden', expanded ? 'false' : 'true'); } function syncBossTabVisibility() { const bossBtn = document.querySelector('.bb-tab-btn[data-bb-tab="boss"]'); if (!bossBtn) { return; } bossBtn.classList.remove('d-none'); } function getCounts(sections) { const mail = sections.mail || {}; const cases = sections.cases || {}; const urgent = sections.urgent || {}; const timer = sections.timer || {}; const kuma = sections.kuma || {}; const eset = sections.eset || {}; const unassigned = sections.unassigned || {}; return { mail: Number(mail.unread || 0), cases: Number(cases.open || 0), urgent: Number(urgent.count || 0), unassigned: Number(unassigned.count || 0), timer: Number(timer.active_count || 0), kuma: Number(kuma.down || 0), eset: Number(eset.incidents || 0) }; } function detailTextFor(key, sections) { const counts = getCounts(sections); const nameMap = { mail: 'Ubesvarede mails', cases: 'Åbne sager', urgent: 'Hastesager', unassigned: 'Sager uden ansvarlig', timer: 'Aktive timere', kuma: 'Kuma alerts', eset: 'ESET incidents' }; const val = counts[key] || 0; return nameMap[key] + ': ' + val; } function severityClassFor(key, value) { const val = Number(value || 0); if (key === 'urgent') { return val > 0 ? 'sev-critical' : 'sev-ok'; } if (key === 'unassigned') { if (val >= 3) return 'sev-critical'; if (val > 0) return 'sev-warn'; return 'sev-ok'; } if (key === 'mail') { if (val >= 10) return 'sev-critical'; if (val > 0) return 'sev-warn'; return 'sev-ok'; } return val > 0 ? 'sev-warn' : 'sev-ok'; } function listFor(key, sections) { const mail = sections.mail || {}; const cases = sections.cases || {}; const urgent = sections.urgent || {}; const timer = sections.timer || {}; const kuma = sections.kuma || {}; const eset = sections.eset || {}; const messages = sections.messages || {}; const tasks = sections.tasks || {}; const boss = sections.boss || {}; function esc(str) { return String(str).replace(/&/g, '&').replace(//g, '>'); } if (key === 'overview') { if (overviewFilter === 'urgent') return urgent.list ? urgent.list.map(u => '
Hastesag: ' + esc(u.title) + '
') : ['Ingen hastesager.']; if (overviewFilter === 'kuma') return kuma.list ? kuma.list.map(k => '
📉 ' + esc(k) + '
') : ['Alle systemer oppe.']; if (overviewFilter === 'eset') return eset.list ? eset.list.map(e => '
🔐 ' + esc(e) + '
') : ['Ingen ESET incidents.']; if (overviewFilter === 'cases') return cases.list ? cases.list.map(c => '
' + esc(c.title) + '
') : ['Ingen åbne sager.']; if (overviewFilter === 'mail') return ['
📧 ' + mail.unread + ' ulæste mails.
💬 ' + mail.customer_reply_needed + ' kræver kundesvar.
']; let out = []; if (urgent.count > 0) out.push('
Hastesager: ' + urgent.count + ' aktive
'); if (mail.unread > 0) out.push('
Ubesvarede mails: ' + mail.unread + '
'); if (cases.open > 0) out.push('
Åbne sager i alt: ' + cases.open + '
'); if (kuma.down > 0) out.push('
Uptime Kuma nedetid: ' + kuma.down + ' enheder
'); if (eset.incidents > 0) out.push('
ESET incidents: ' + eset.incidents + '
'); if (out.length === 0) { out.push('
🎉 Alt ser grønt ud! Intet kritisk lige nu.
'); } // Add quick note button on overview out.push('
'); return out; } if (key === 'timer') { if (timer.active_count > 0) { return (timer.list || []).map(t => { const elapsedText = t.elapsed_hhmmss || (String(t.elapsed || 0) + 's'); return '
' + esc(t.desc) + ' (' + esc(elapsedText) + ')
'; }); } return ['Ingen aktive timere lige nu.']; } if (key === 'messages') { if (messages.count > 0) { return (messages.list || []).map(m => '
' + esc(m.from) + ': ' + esc(m.text) + '
'); } return ['Ingen nye beskeder.']; } if (key === 'tasks') { if (tasks.count > 0) { return (tasks.list || []).map(t => '
' + esc(t.title) + ' ' + esc(t.deadline) + '
'); } return ['Ingen aktuelle opgaver.']; } if (key === 'boss') { const stats = boss.stats || {}; const workload = Array.isArray(boss.team_workload) ? boss.team_workload : []; const techniciansToday = Array.isArray(boss.technicians_today) ? boss.technicians_today : []; const escalations = Array.isArray(boss.escalations) ? boss.escalations : []; const unassigned = Array.isArray(boss.unassigned_cases) ? boss.unassigned_cases : []; const out = [ '
' + '
Åbne sager
' + Number(stats.open_cases || 0) + '
' + '
Hastesager
' + Number(stats.urgent_cases || 0) + '
' + '
Uden ansvarlig
' + Number(stats.unassigned || 0) + '
' + '
Stale >24t
' + Number(stats.stale_urgent_cases || 0) + '
' + '
', '
' + '' + '' + '' + '' + '
' ]; if (workload.length > 0) { out.push('
Team-belastning
'); workload.slice(0, 5).forEach(function (w) { out.push( '
' + '
' + esc(w.owner_name || 'Ukendt') + '
Åbne: ' + Number(w.open_cases || 0) + ' • Haste: ' + Number(w.urgent_cases || 0) + '
' + '' + '
' ); }); } if (techniciansToday.length > 0) { out.push('
Teknikernes opgaver i dag
'); techniciansToday.slice(0, 6).forEach(function (tech) { const todayTasks = Array.isArray(tech.today_tasks) ? tech.today_tasks : []; let tasksHtml = '
Ingen opgaver i dag.
'; if (todayTasks.length > 0) { tasksHtml = '
' + todayTasks.slice(0, 3).map(function (task) { return '
' + esc(task.title || ('Sag #' + task.id)) + '
'; }).join('') + '
'; } out.push( '
' + '
' + '
' + esc(tech.owner_name || 'Tekniker') + '
I dag: ' + Number(tech.due_today_cases || 0) + ' • Åbne: ' + Number(tech.open_cases || 0) + '
' + '
' + '' + '' + '
' + '
' + tasksHtml + '
' ); }); } if (escalations.length > 0) { out.push('
Eskaleringer
'); escalations.slice(0, 4).forEach(function (c) { const ageHours = Math.floor(Number(c.age_seconds || 0) / 3600); out.push( '
' + '
' + esc(c.title || 'Sag') + '
' + esc(c.owner_name || 'Ikke tildelt') + ' • ' + ageHours + 't siden opdatering
' + '' + '
' ); }); } if (unassigned.length > 0) { out.push('
Ufordelte sager
'); unassigned.slice(0, 4).forEach(function (c) { out.push( '
' + '
' + esc(c.title || 'Sag') + '
Prioritet: ' + esc(c.priority || 'normal') + '
' + '' + '
' ); }); } return out; } return ['Klik rundt i menuen for at se data.']; } function updateBar(sections) { const counts = getCounts(sections); const keys = Object.keys(counts); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const chipText = document.querySelector('.bb-chip[data-bb-key="' + key + '"] .bb-chip-text'); const chipLabel = document.querySelector('.bb-chip[data-bb-key="' + key + '"] .bb-chip-label'); const chipBubble = document.querySelector('.bb-chip[data-bb-key="' + key + '"] .bb-chip-bubble'); const chip = document.querySelector('.bb-chip[data-bb-key="' + key + '"]'); if (chipText && chip) { const val = counts[key]; const labels = { mail: 'Ulæste mails', cases: 'Sager', urgent: 'Hastesager', unassigned: 'Uden ansvarlig', timer: 'Timere', kuma: 'Kuma', eset: 'ESET' }; if (chipLabel) { chipLabel.textContent = labels[key]; } if (chipBubble) { chipBubble.textContent = String(val); } chipText.textContent = labels[key] + ': ' + val; chip.classList.toggle('has-items', val > 0); chip.classList.remove('sev-ok', 'sev-warn', 'sev-critical'); chip.classList.add(severityClassFor(key, val)); chip.setAttribute('title', detailTextFor(key, sections)); chip.setAttribute('aria-label', detailTextFor(key, sections)); } } } function bindChipHoverPreview() { const chips = document.querySelectorAll('.bb-chip'); const detail = byId('bbCountDetail'); if (!detail || !chips.length) { return; } chips.forEach(function (chip) { chip.addEventListener('mouseenter', function () { const key = chip.getAttribute('data-bb-key'); if (!key) return; detail.innerHTML = ' ' + detailTextFor(key, latestSections); }); chip.addEventListener('mouseleave', function () { detail.innerHTML = ' Klik på en kategori for at se detaljer'; }); }); } function updateActivityZone() { const timerChip = byId('bbActiveTimerChip'); const timerText = byId('bbActiveTimerText'); const notifCount = byId('bbNotificationsCount'); const timer = ((latestSections || {}).timer || {}).active || {}; const hasActiveTimer = !!timer.active; if (timerChip && timerText) { timerChip.classList.toggle('is-hidden', !hasActiveTimer); if (hasActiveTimer) { const elapsed = timer.elapsed_hhmmss || '00:00:00'; const name = timer.sag_navn || ('Sag #' + (timer.sag_id || '')); timerText.textContent = name + ' - ' + elapsed; } } if (notifCount) { const computed = Number(latestNotificationCount || ((latestSections.messages || {}).count || 0)); notifCount.textContent = String(computed); } } function renderTabPanel() { const titleContainer = byId('bbTabTitle'); const innerContent = byId('bbTabInnerContent'); if (!titleContainer || !innerContent) { return; } const titleText = titleContainer.querySelector('.bb-tab-title-text'); const titleByKey = { overview: 'Overblik', timer: 'Timere', messages: 'Beskeder', tasks: 'Opgaver', boss: 'Chef Dashboard' }; const iconByKey = { overview: 'bi-bell', timer: 'bi-stopwatch', messages: 'bi-chat-dots', tasks: 'bi-calendar-check', boss: 'bi-person-workspace' }; const activeTitle = titleByKey[activeKey] || 'Info'; if (titleText) { titleText.textContent = activeTitle; } else { titleContainer.textContent = activeTitle; } const iconSpan = titleContainer.querySelector('.bi'); if (iconSpan) { iconSpan.className = 'bi ' + (iconByKey[activeKey] || 'bi-info-circle') + ' me-2 text-accent'; } // Render rich UI lists const lines = listFor(activeKey, latestSections); const ul = document.createElement('ul'); ul.className = 'bb-tab-list'; lines.forEach(function (line) { const li = document.createElement('li'); // Allow rich HTML (buttons, inputs) - assuming listFor provides sanitized data wrapped in markup li.innerHTML = line; ul.appendChild(li); }); innerContent.innerHTML = ''; // Add specific headers/controls based on active tab if (activeKey === 'tasks') { const topBar = document.createElement('div'); topBar.className = 'bb-task-actions mb-3'; topBar.innerHTML = ''; innerContent.appendChild(topBar); } if (activeKey === 'messages') { const chatContainer = document.createElement('div'); chatContainer.className = 'd-flex flex-column h-100'; ul.classList.add('flex-grow-1', 'mb-3'); const replyBox = document.createElement('div'); replyBox.className = 'mt-2 border-top pt-2 border-primary-subtle'; replyBox.innerHTML = `
`; chatContainer.appendChild(ul); chatContainer.appendChild(replyBox); innerContent.appendChild(chatContainer); // Fetch users dynamically fetch('/api/v1/users?is_active=true', { credentials: 'include' }) .then(r => r.json()) .then(payload => { const users = Array.isArray(payload) ? payload : ((payload && payload.data && Array.isArray(payload.data)) ? payload.data : []); const sel = document.getElementById('chatRecipient'); if (sel) { sel.innerHTML = ''; users.forEach(u => { sel.innerHTML += ``; }); } }) .catch(e => console.error("Error fetching users for chat:", e)); } else { innerContent.appendChild(ul); } } function bindSideTabs() { const buttons = document.querySelectorAll('.bb-tab-btn'); for (let i = 0; i < buttons.length; i++) { buttons[i].addEventListener('click', function (e) { // Clear filter on direct human click of the button, unless we programmatically called click() if (e.isTrusted) overviewFilter = null; for (let j = 0; j < buttons.length; j++) { buttons[j].classList.remove('is-active'); buttons[j].setAttribute('aria-selected', 'false'); } this.classList.add('is-active'); this.setAttribute('aria-selected', 'true'); activeKey = this.getAttribute('data-bb-tab'); renderTabPanel(); const detail = byId('bbCountDetail'); if (detail) { detail.innerHTML = ' Viser: ' + (activeKey.charAt(0).toUpperCase() + activeKey.slice(1)); } }); } } function bindChipClicks() { const chips = document.querySelectorAll('.bb-chip'); for (let i = 0; i < chips.length; i++) { chips[i].addEventListener('click', function () { const key = this.getAttribute('data-bb-key'); if (!key) return; const detail = byId('bbCountDetail'); if (detail) { detail.innerHTML = ' ' + detailTextFor(key, latestSections); } const routes = { mail: '/emails', urgent: '/sag?priority=urgent', unassigned: '/sag?unassigned=true', timer: '/timetracking', cases: '/sag' }; const route = routes[key] || '/dashboard'; window.location.href = route; }); } } function bindSheetToggle() { const toggle = byId('bbSheetToggle'); if (!toggle) return; toggle.addEventListener('click', function () { const shell = byId('globalBottomBar'); if (!shell) return; const isExp = shell.classList.contains('is-expanded'); setExpanded(!isExp); }); } function stopPolling() { if (pollTimer) { window.clearTimeout(pollTimer); pollTimer = null; } } function pollOnce() { fetchBottomBarState().then(function (data) { applyState(data); }).catch(function (err) { console.warn('Bottom bar poll failed', err); }).finally(function () { pollTimer = window.setTimeout(pollOnce, 15000); }); } function startPollingFallback() { stopPolling(); pollOnce(); } function updateFromRealtimeEvent(payload) { if (!payload || !payload.event) { return; } if (payload.event === 'timer_tick') { const timer = payload.data || {}; latestSections.timer = latestSections.timer || {}; latestSections.timer.active = timer; latestSections.timer.active_count = timer.active ? 1 : 0; latestSections.timer.list = timer.active ? [{ id: timer.time_entry_id, sag_id: timer.sag_id, desc: timer.sag_navn || ('Sag #' + (timer.sag_id || '')), elapsed: timer.elapsed, elapsed_hhmmss: timer.elapsed_hhmmss }] : []; } if (payload.event === 'status_delta') { const status = payload.data || {}; latestSections.mail = latestSections.mail || {}; latestSections.cases = latestSections.cases || {}; latestSections.urgent = latestSections.urgent || {}; latestSections.unassigned = latestSections.unassigned || {}; latestSections.boss = latestSections.boss || { stats: {} }; latestSections.mail.unread = Number(status.mails_unread || 0); latestSections.mail.customer_reply_needed = Number(status.mails_unread || 0); latestSections.cases.open = Number(status.sager_open || 0); latestSections.urgent.count = Number(status.sager_urgent || 0); latestSections.unassigned.count = Number(status.sager_unassigned || 0); latestSections.boss.stats = latestSections.boss.stats || {}; latestSections.boss.stats.unassigned = Number(status.sager_unassigned || 0); } if (payload.event === 'notification_delta') { const notifications = payload.data || {}; const items = Array.isArray(notifications.items) ? notifications.items : []; latestSections.messages = latestSections.messages || {}; latestSections.tasks = latestSections.tasks || {}; latestSections.messages.count = items.length; latestSections.messages.list = items.slice(0, 5).map(function (item) { return { from: (item.type || 'System').toString(), text: (item.title || item.message || 'Notifikation').toString() }; }); latestSections.tasks.count = items.length; latestSections.tasks.list = items.slice(0, 5).map(function (item) { return { title: item.title || 'Notifikation', deadline: item.severity || 'info' }; }); latestNotificationCount = Number(notifications.count || items.length || 0); latestNotifications = items; } syncBossTabVisibility(); updateBar(latestSections); updateActivityZone(); renderTabPanel(); } function scheduleWsReconnect() { if (wsReconnectTimer) { window.clearTimeout(wsReconnectTimer); } wsReconnectTimer = window.setTimeout(connectRealtime, 3000); } function connectRealtime() { if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) { return; } const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = protocol + '//' + window.location.host + '/api/v1/bottom-bar/ws'; try { ws = new WebSocket(wsUrl); } catch (err) { console.warn('Bottom bar websocket init failed', err); startPollingFallback(); scheduleWsReconnect(); return; } ws.addEventListener('open', function () { stopPolling(); }); ws.addEventListener('message', function (event) { try { const payload = JSON.parse(event.data || '{}'); updateFromRealtimeEvent(payload); } catch (err) { console.warn('Bottom bar websocket parse error', err); } }); ws.addEventListener('close', function () { startPollingFallback(); scheduleWsReconnect(); }); ws.addEventListener('error', function (err) { console.warn('Bottom bar websocket error', err); startPollingFallback(); }); } function stopActiveTimer() { const active = ((latestSections || {}).timer || {}).active || {}; if (!active.time_entry_id) { return Promise.resolve(); } return fetch('/api/v1/timetracking/time/stop', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ time_id: active.time_entry_id }) }).catch(function (err) { console.warn('Failed stopping active timer', err); }); } function bindHeaderActions() { const searchBtn = byId('bbSearchBtn'); const notificationsBtn = byId('bbNotificationsBtn'); const userStatusBtn = byId('bbUserStatusBtn'); const pauseBtn = byId('bbTimerPauseBtn'); const stopBtn = byId('bbTimerStopBtn'); const switchBtn = byId('bbTimerSwitchBtn'); const timerChip = byId('bbActiveTimerChip'); if (searchBtn) { searchBtn.addEventListener('click', function () { const trigger = byId('globalSearchBtn'); if (trigger) { trigger.click(); } }); } if (notificationsBtn) { notificationsBtn.addEventListener('click', function () { if (latestNotifications.length > 0) { const first = latestNotifications[0] || {}; if (first.action) { window.location.href = first.action; return; } } const trigger = byId('globalRemindersBtn'); if (trigger) { trigger.click(); } }); } if (userStatusBtn) { userStatusBtn.addEventListener('click', function () { const trigger = document.querySelector('[data-bs-target="#profileModal"]'); if (trigger) { trigger.click(); } }); } if (pauseBtn) { pauseBtn.addEventListener('click', function () { stopActiveTimer(); }); } if (stopBtn) { stopBtn.addEventListener('click', function () { stopActiveTimer(); }); } if (switchBtn) { switchBtn.addEventListener('click', function () { window.location.href = '/sag'; }); } if (timerChip) { timerChip.addEventListener('click', function () { window.location.href = '/timetracking'; }); } document.addEventListener('click', function (e) { const actionBtn = e.target && e.target.closest('[data-bb-create]'); if (!actionBtn) return; const actionKey = actionBtn.getAttribute('data-bb-create'); const actions = (latestContextActions.context || []).concat(latestContextActions.global || []); const matched = actions.find(function (item) { return item.id === actionKey; }); if (actionKey === 'new_case') { const quickBtn = byId('quickCreateBtn'); if (quickBtn) { quickBtn.click(); return; } } if (matched && matched.action) { window.location.href = matched.action; } }); } function bindDynamicActions() { document.addEventListener('click', function (e) { const target = e.target; const btn = target && target.closest('button'); if (!btn) return; const bossAction = btn.getAttribute('data-boss-action'); if (bossAction) { if (bossAction === 'assign_next_to_owner') { const ownerId = Number(btn.getAttribute('data-owner-id') || 0); if (ownerId <= 0) { return; } const originalHtml = btn.innerHTML; btn.disabled = true; btn.innerHTML = 'Tildeler...'; fetch('/api/v1/bottom-bar/boss/assign-next-to-user', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ assignee_user_id: ownerId }) }) .then(async r => { const body = await r.json().catch(() => ({})); if (!r.ok) { const detail = body && body.detail ? body.detail : 'Kunne ikke tildele næste sag'; throw new Error(typeof detail === 'string' ? detail : 'Kunne ikke tildele næste sag'); } return body; }) .then(data => { const detail = byId('bbCountDetail'); if (detail) { if (data.status === 'assigned' && data.case) { detail.innerHTML = ' Tildelt: ' + escapeHtml(data.case.title || 'Sag') + ' til tekniker.'; } else { detail.innerHTML = ' ' + escapeHtml(data.message || 'Ingen sager at tildele'); } } return fetchBottomBarState(); }) .then(applyState) .catch(err => { console.error('Assign next to owner failed', err); const detail = byId('bbCountDetail'); if (detail) { detail.innerHTML = ' ' + escapeHtml(err.message || 'Fejl ved tildeling'); } }) .finally(() => { btn.disabled = false; btn.innerHTML = originalHtml; }); return; } if (bossAction === 'auto_assign_next') { const originalHtml = btn.innerHTML; btn.disabled = true; btn.innerHTML = 'Fordeler...'; fetch('/api/v1/bottom-bar/boss/auto-assign-next', { method: 'POST', credentials: 'include' }) .then(async r => { const body = await r.json().catch(() => ({})); if (!r.ok) { const detail = body && body.detail ? body.detail : 'Kunne ikke auto-fordele sag'; throw new Error(typeof detail === 'string' ? detail : 'Kunne ikke auto-fordele sag'); } return body; }) .then(data => { const detail = byId('bbCountDetail'); if (detail) { if (data.status === 'assigned' && data.case && data.assignee) { detail.innerHTML = ' Auto-fordelt: ' + escapeHtml(data.case.title || 'Sag') + ' → ' + escapeHtml(data.assignee.name || 'medarbejder'); } else { detail.innerHTML = ' ' + escapeHtml(data.message || 'Ingen sager at fordele'); } } return fetchBottomBarState(); }) .then(applyState) .catch(err => { console.error('Boss auto-assign failed', err); const detail = byId('bbCountDetail'); if (detail) { detail.innerHTML = ' ' + escapeHtml(err.message || 'Fejl ved auto-fordeling'); } }) .finally(() => { btn.disabled = false; btn.innerHTML = originalHtml; }); return; } if (bossAction === 'open_unassigned') { window.location.href = '/sag?unassigned=true'; return; } if (bossAction === 'open_escalations') { window.location.href = '/sag?priority=urgent'; return; } if (bossAction === 'open_team') { window.location.href = '/timetracking'; return; } if (bossAction === 'open_owner') { const ownerId = Number(btn.getAttribute('data-owner-id') || 0); window.location.href = ownerId > 0 ? ('/sag?ansvarlig=' + ownerId) : '/sag'; return; } if (bossAction === 'open_case') { const caseId = Number(btn.getAttribute('data-case-id') || 0); window.location.href = caseId > 0 ? ('/sag/' + caseId) : '/sag'; return; } } if (btn.id === 'btnNextTask') { console.log("-> Beder backend om næste opgave..."); btn.innerHTML = ' Omsætter kalender og SLA...'; btn.disabled = true; fetch('/api/v1/bottom-bar/next_task', { method: 'POST', credentials: 'include' }) .then(r => { if (!r.ok) { throw new Error('Kunne ikke hente næste opgave'); } return r.json(); }) .then(data => { const task = data && data.task ? data.task : {}; const taskTitle = task.title || 'Ingen opgave fundet'; const caseId = task.case_id || '-'; const freeMins = data && data.free_time_calculated ? data.free_time_calculated : 0; btn.innerHTML = 'Du fik tildelt: ' + taskTitle + ' (Sag #' + caseId + ') ' + freeMins + 'm fri'; btn.classList.add('btn-success'); btn.classList.remove('btn-primary'); }) .catch(err => { console.error("Fejl:", err); btn.innerHTML = "Fejl - prøv igen"; btn.disabled = false; }); } if (btn.id === 'btnSendMsg') { const input = document.getElementById('chatInputQuick'); const recipientObj = document.getElementById('chatRecipient'); if (input && input.value.trim() !== '') { const recipient = recipientObj ? recipientObj.options[recipientObj.selectedIndex].text : 'Alle'; console.log("-> Sender besked til", recipient, ":", input.value); const msgVal = input.value; input.value = ''; const msgContainer = document.createElement('div'); msgContainer.className = 'mb-2 text-end'; msgContainer.innerHTML = '
Til: ' + escapeHtml(recipient) + '
Mig: ' + escapeHtml(msgVal) + '
'; const chatContainer = document.querySelector('#bbTabInnerContent .d-flex.flex-column.h-100'); if (!chatContainer) { return; } const listUl = chatContainer.querySelector('ul.bb-tab-list'); if (!listUl) { return; } listUl.appendChild(msgContainer); // Simple hacky scroll down const tabInner = document.getElementById('bbTabInnerContent'); if(tabInner) { tabInner.scrollTop = tabInner.scrollHeight + 500; } } } }); } async function loadUserStatus() { const el = byId('bbUserStatusText'); if (!el) return; try { const res = await fetch('/api/v1/auth/me/profile', { credentials: 'include' }); if (!res.ok) return; const payload = await res.json(); const user = (payload || {}).user || payload || {}; const name = user.full_name || user.username || user.email || 'Bruger'; el.textContent = name; } catch (_err) { // Ignore user status load errors to keep bar non-blocking. } } document.addEventListener('DOMContentLoaded', function () { activeKey = 'overview'; // Default overview state bindChipClicks(); bindChipHoverPreview(); bindSheetToggle(); bindHeaderActions(); bindDynamicActions(); bindSideTabs(); loadUserStatus(); startPollingFallback(); connectRealtime(); }); })();