bmc_hub/app/ticket/frontend/mockups/tech_v1_overview.html

601 lines
30 KiB
HTML
Raw Permalink Normal View History

{% extends "shared/frontend/base.html" %}
{% block title %}Tekniker Dashboard V1 - Overblik{% endblock %}
{% block extra_css %}
<style>
#caseTable thead th {
white-space: nowrap;
font-size: 0.78rem;
letter-spacing: 0.02em;
}
#caseTable tbody td {
font-size: 0.84rem;
vertical-align: top;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 mb-4">
<div>
<h1 class="h3 mb-1">🛠️ Tekniker Dashboard V1</h1>
<p class="text-muted mb-0">Kort overblik for {{ technician_name }} (bruger #{{ technician_user_id }})</p>
</div>
<div class="d-flex gap-2">
<a href="/ticket/dashboard/technician?technician_user_id={{ technician_user_id }}" class="btn btn-outline-secondary btn-sm">Tilbage til valg</a>
<a href="/ticket/dashboard/technician/v2?technician_user_id={{ technician_user_id }}" class="btn btn-outline-primary btn-sm">Se V2</a>
<a href="/ticket/dashboard/technician/v3?technician_user_id={{ technician_user_id }}" class="btn btn-outline-primary btn-sm">Se V3</a>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-6 col-lg-2">
<div class="card border-0 shadow-sm kpi-toggle" onclick="toggleSection('newCases')" id="kpiNewCases" style="cursor: pointer; transition: all 0.2s;">
<div class="card-body text-center">
<div class="small text-muted">Nye sager</div>
<div class="h4 mb-0">{{ kpis.new_cases_count }}</div>
</div>
</div>
</div>
<div class="col-6 col-lg-2">
<div class="card border-0 shadow-sm kpi-toggle" onclick="toggleSection('myCases')" id="kpiMyCases" style="cursor: pointer; transition: all 0.2s;">
<div class="card-body text-center">
<div class="small text-muted">Mine sager</div>
<div class="h4 mb-0">{{ kpis.my_cases_count }}</div>
</div>
</div>
</div>
<div class="col-6 col-lg-2">
<div class="card border-0 shadow-sm kpi-toggle" onclick="toggleSection('todayTasks')" id="kpiTodayTasks" style="cursor: pointer; transition: all 0.2s;">
<div class="card-body text-center">
<div class="small text-muted">Dagens opgaver</div>
<div class="h4 mb-0">{{ kpis.today_tasks_count }}</div>
</div>
</div>
</div>
<div class="col-6 col-lg-2">
<div class="card border-0 shadow-sm kpi-toggle" onclick="toggleSection('groupCases')" id="kpiGroupCases" style="cursor: pointer; transition: all 0.2s;">
<div class="card-body text-center">
<div class="small text-muted">Gruppe-sager</div>
<div class="h4 mb-0">{{ kpis.group_cases_count }}</div>
</div>
</div>
</div>
<div class="col-6 col-lg-2"><div class="card border-0 shadow-sm"><div class="card-body text-center"><div class="small text-muted">Haste / over SLA</div><div class="h4 mb-0 text-danger">{{ kpis.urgent_overdue_count }}</div></div></div></div>
<div class="col-6 col-lg-2"><div class="card border-0 shadow-sm"><div class="card-body text-center"><div class="small text-muted">Mine opportunities</div><div class="h4 mb-0">{{ kpis.my_opportunities_count }}</div></div></div></div>
</div>
<!-- Liste og detalje område -->
<div class="row g-4">
<div class="col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="mb-0" id="listTitle">Alle sager</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0" id="caseTable">
<thead class="table-light" id="tableHead">
<tr>
<th>SagsID</th>
<th>Virksom.</th>
<th>Kontakt</th>
<th>Beskr.</th>
<th>Type</th>
<th>Prioritet</th>
<th>Ansvarl.</th>
<th>Gruppe/Level</th>
<th>Opret.</th>
<th>Start arbejde</th>
<th>Start inden</th>
<th>Deadline</th>
</tr>
</thead>
<tbody id="tableBody">
<tr><td colspan="12" class="text-center text-muted py-3">Vælg et filter ovenfor</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Placeholder -->
<div class="card border-0 shadow-sm" id="placeholderPanel">
<div class="card-body text-center py-5">
<i class="bi bi-arrow-left-circle text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3 mb-0">Klik på en sag i listen for at se detaljer</p>
</div>
</div>
<!-- Detail panel -->
<div class="card border-0 shadow-sm" id="detailPanel" style="display: none; max-height: 85vh; overflow-y: auto;">
<!-- Header -->
<div class="card-header bg-white border-bottom sticky-top d-flex justify-content-between align-items-center py-2">
<div>
<span class="small text-muted" id="detailCaseBadge"></span>
<h6 class="mb-0 fw-bold" id="detailTitle">Sag</h6>
</div>
<div class="d-flex gap-1">
<a id="detailOpenBtn" href="#" class="btn btn-sm btn-outline-primary" target="_blank">
<i class="bi bi-box-arrow-up-right"></i>
</a>
<button class="btn btn-sm btn-outline-secondary" onclick="closeDetail()">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
<!-- Module pills (mouseover viser indhold) -->
<div class="px-3 pt-2 pb-1 border-bottom" id="modulePills" style="display:none;"></div>
<!-- Contact row (ring / SMS) -->
<div class="px-3 py-2 border-bottom" id="contactRow" style="display:none;"></div>
<!-- Status + meta -->
<div class="px-3 py-2 border-bottom" id="detailMeta"></div>
<!-- Kommentar-feed -->
<div class="px-3 pt-2">
<div class="small text-muted fw-semibold mb-2">Kommentarer</div>
<div id="kommentarFeed" style="max-height: 220px; overflow-y: auto;"></div>
<!-- Skriv kommentar -->
<div class="mt-2">
<textarea class="form-control form-control-sm" id="newKommentar" rows="2" placeholder="Skriv en kommentar..."></textarea>
<button class="btn btn-sm btn-primary mt-1 w-100" onclick="postKommentar()">
<i class="bi bi-chat-left-text"></i> Send kommentar
</button>
</div>
</div>
<!-- Tid & Fakturering -->
<div class="px-3 py-2 mt-1 border-top">
<div class="small text-muted fw-semibold mb-2">Tid & Fakturering</div>
<div class="row g-2">
<div class="col-6">
<input type="number" class="form-control form-control-sm" id="tidMinutter" placeholder="Min." min="1" step="15">
</div>
<div class="col-6">
<select class="form-select form-select-sm" id="tidAfregning">
<option value="invoice">Faktura</option>
<option value="prepaid">Forudbetalt</option>
<option value="internal">Intern</option>
<option value="warranty">Garanti</option>
</select>
</div>
<div class="col-12">
<input type="text" class="form-control form-control-sm" id="tidBeskrivelse" placeholder="Beskrivelse (hvad lavede du?)">
</div>
</div>
<button class="btn btn-sm btn-success mt-1 w-100" onclick="registrerTid()">
<i class="bi bi-clock-history"></i> Registrer tid
</button>
</div>
</div>
</div>
</div>
</div>
<script>
let currentFilter = null;
const allData = {
newCases: [
{% for item in new_cases %}
{
id: {{ item.id }},
titel: {{ item.titel | tojson | safe }},
beskrivelse: {{ item.beskrivelse | tojson | safe if item.beskrivelse else 'null' }},
priority: {{ item.priority | tojson | safe if item.priority else 'null' }},
customer_name: {{ item.customer_name | tojson | safe }},
kontakt_navn: {{ item.kontakt_navn | tojson | safe if item.kontakt_navn else 'null' }},
case_type: {{ item.case_type | tojson | safe if item.case_type else 'null' }},
ansvarlig_navn: {{ item.ansvarlig_navn | tojson | safe if item.ansvarlig_navn else 'null' }},
assigned_group_name: {{ item.assigned_group_name | tojson | safe if item.assigned_group_name else 'null' }},
created_at: {{ item.created_at.isoformat() | tojson | safe if item.created_at else 'null' }},
start_date: {{ item.start_date.isoformat() | tojson | safe if item.start_date else 'null' }},
deferred_until: {{ item.deferred_until.isoformat() | tojson | safe if item.deferred_until else 'null' }},
status: {{ item.status | tojson | safe if item.status else 'null' }},
deadline: {{ item.deadline.isoformat() | tojson | safe if item.deadline else 'null' }}
}{% if not loop.last %},{% endif %}
{% endfor %}
],
myCases: [
{% for item in my_cases %}
{
id: {{ item.id }},
titel: {{ item.titel | tojson | safe }},
beskrivelse: {{ item.beskrivelse | tojson | safe if item.beskrivelse else 'null' }},
priority: {{ item.priority | tojson | safe if item.priority else 'null' }},
customer_name: {{ item.customer_name | tojson | safe }},
kontakt_navn: {{ item.kontakt_navn | tojson | safe if item.kontakt_navn else 'null' }},
case_type: {{ item.case_type | tojson | safe if item.case_type else 'null' }},
ansvarlig_navn: {{ item.ansvarlig_navn | tojson | safe if item.ansvarlig_navn else 'null' }},
assigned_group_name: {{ item.assigned_group_name | tojson | safe if item.assigned_group_name else 'null' }},
created_at: {{ item.created_at.isoformat() | tojson | safe if item.created_at else 'null' }},
start_date: {{ item.start_date.isoformat() | tojson | safe if item.start_date else 'null' }},
deferred_until: {{ item.deferred_until.isoformat() | tojson | safe if item.deferred_until else 'null' }},
status: {{ item.status | tojson | safe if item.status else 'null' }},
deadline: {{ item.deadline.isoformat() | tojson | safe if item.deadline else 'null' }}
}{% if not loop.last %},{% endif %}
{% endfor %}
],
todayTasks: [
{% for item in today_tasks %}
{
item_type: {{ item.item_type | tojson | safe }},
item_id: {{ item.item_id }},
title: {{ item.title | tojson | safe }},
beskrivelse: {{ item.beskrivelse | tojson | safe if item.beskrivelse else 'null' }},
customer_name: {{ item.customer_name | tojson | safe }},
kontakt_navn: {{ item.kontakt_navn | tojson | safe if item.kontakt_navn else 'null' }},
task_reason: {{ item.task_reason | tojson | safe if item.task_reason else 'null' }},
created_at: {{ item.created_at.isoformat() | tojson | safe if item.created_at else 'null' }},
start_date: {{ item.start_date.isoformat() | tojson | safe if item.start_date else 'null' }},
deferred_until: {{ item.deferred_until.isoformat() | tojson | safe if item.deferred_until else 'null' }},
case_type: {{ item.case_type | tojson | safe if item.case_type else 'null' }},
ansvarlig_navn: {{ item.ansvarlig_navn | tojson | safe if item.ansvarlig_navn else 'null' }},
assigned_group_name: {{ item.assigned_group_name | tojson | safe if item.assigned_group_name else 'null' }},
priority: {{ item.priority | tojson | safe if item.priority else 'null' }},
status: {{ item.status | tojson | safe if item.status else 'null' }}
}{% if not loop.last %},{% endif %}
{% endfor %}
],
groupCases: [
{% for item in group_cases %}
{
id: {{ item.id }},
titel: {{ item.titel | tojson | safe }},
group_name: {{ item.group_name | tojson | safe }},
beskrivelse: {{ item.beskrivelse | tojson | safe if item.beskrivelse else 'null' }},
priority: {{ item.priority | tojson | safe if item.priority else 'null' }},
customer_name: {{ item.customer_name | tojson | safe }},
kontakt_navn: {{ item.kontakt_navn | tojson | safe if item.kontakt_navn else 'null' }},
case_type: {{ item.case_type | tojson | safe if item.case_type else 'null' }},
ansvarlig_navn: {{ item.ansvarlig_navn | tojson | safe if item.ansvarlig_navn else 'null' }},
assigned_group_name: {{ item.assigned_group_name | tojson | safe if item.assigned_group_name else 'null' }},
created_at: {{ item.created_at.isoformat() | tojson | safe if item.created_at else 'null' }},
start_date: {{ item.start_date.isoformat() | tojson | safe if item.start_date else 'null' }},
deferred_until: {{ item.deferred_until.isoformat() | tojson | safe if item.deferred_until else 'null' }},
status: {{ item.status | tojson | safe if item.status else 'null' }},
deadline: {{ item.deadline.isoformat() | tojson | safe if item.deadline else 'null' }}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
};
function formatDate(dateStr) {
if (!dateStr) return '-';
const d = new Date(dateStr);
return d.toLocaleDateString('da-DK', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function formatShortDate(dateStr) {
if (!dateStr) return '-';
const d = new Date(dateStr);
return d.toLocaleDateString('da-DK', { day: '2-digit', month: '2-digit', year: 'numeric' });
}
function renderCaseTableRow(item, idField = 'id', typeField = 'case') {
const itemId = item[idField];
const openType = typeField === 'item_type' ? item.item_type : 'case';
const description = item.beskrivelse || item.titel || item.title || '-';
const typeValue = item.case_type || item.item_type || '-';
const groupLevel = item.assigned_group_name || item.group_name || '-';
const priorityValue = item.priority || 'normal';
return `
<tr onclick="showCaseDetails(${itemId}, '${openType}')" style="cursor:pointer;">
<td>#${itemId}</td>
<td>${item.customer_name || '-'}</td>
<td>${item.kontakt_navn || '-'}</td>
<td>${description}</td>
<td>${typeValue}</td>
<td>${priorityValue}</td>
<td>${item.ansvarlig_navn || '-'}</td>
<td>${groupLevel}</td>
<td>${formatShortDate(item.created_at)}</td>
<td>${formatShortDate(item.start_date)}</td>
<td>${formatShortDate(item.deferred_until)}</td>
<td>${formatShortDate(item.deadline)}</td>
</tr>
`;
}
function toggleSection(filterName) {
const kpiCard = document.getElementById('kpi' + filterName.charAt(0).toUpperCase() + filterName.slice(1));
const listTitle = document.getElementById('listTitle');
const tableBody = document.getElementById('tableBody');
// Reset all KPI cards
document.querySelectorAll('.kpi-toggle').forEach(card => {
card.style.background = '';
card.style.color = '';
const label = card.querySelector('.text-muted');
if (label) label.style.color = '';
});
// If clicking same filter, clear it
if (currentFilter === filterName) {
currentFilter = null;
listTitle.textContent = 'Alle sager';
tableBody.innerHTML = '<tr><td colspan="12" class="text-center text-muted py-3">Vælg et filter ovenfor</td></tr>';
return;
}
// Highlight selected KPI
kpiCard.style.background = 'linear-gradient(135deg, var(--accent), var(--accent-dark, #084c75))';
kpiCard.style.color = 'white';
const label = kpiCard.querySelector('.text-muted');
if (label) label.style.color = 'rgba(255,255,255,0.85)';
// Apply filter and populate table
currentFilter = filterName;
filterAndPopulateTable(filterName);
}
function filterAndPopulateTable(filterName) {
const listTitle = document.getElementById('listTitle');
const tableBody = document.getElementById('tableBody');
let bodyHTML = '';
if (filterName === 'newCases') {
listTitle.innerHTML = '<i class="bi bi-inbox-fill text-primary"></i> Nye sager';
const data = allData.newCases || [];
if (data.length === 0) {
bodyHTML = '<tr><td colspan="12" class="text-center text-muted py-3">Ingen nye sager</td></tr>';
} else {
bodyHTML = data.map(item => renderCaseTableRow(item)).join('');
}
} else if (filterName === 'myCases') {
listTitle.innerHTML = '<i class="bi bi-person-check-fill text-success"></i> Mine sager';
const data = allData.myCases || [];
if (data.length === 0) {
bodyHTML = '<tr><td colspan="12" class="text-center text-muted py-3">Ingen sager tildelt</td></tr>';
} else {
bodyHTML = data.map(item => renderCaseTableRow(item)).join('');
}
} else if (filterName === 'todayTasks') {
listTitle.innerHTML = '<i class="bi bi-calendar-check text-primary"></i> Dagens opgaver';
const data = allData.todayTasks || [];
if (data.length === 0) {
bodyHTML = '<tr><td colspan="12" class="text-center text-muted py-3">Ingen opgaver i dag</td></tr>';
} else {
bodyHTML = data.map(item => {
const normalized = {
...item,
id: item.item_id,
titel: item.title,
beskrivelse: item.task_reason || item.beskrivelse,
deadline: item.deadline || item.due_at,
case_type: item.case_type || item.item_type
};
return renderCaseTableRow(normalized, 'id', 'item_type');
}).join('');
}
} else if (filterName === 'groupCases') {
listTitle.innerHTML = '<i class="bi bi-people-fill text-info"></i> Gruppe-sager';
const data = allData.groupCases || [];
if (data.length === 0) {
bodyHTML = '<tr><td colspan="12" class="text-center text-muted py-3">Ingen gruppe-sager</td></tr>';
} else {
bodyHTML = data.map(item => renderCaseTableRow(item)).join('');
}
}
tableBody.innerHTML = bodyHTML;
}
async function showCaseDetails(id, type) {
const detailPanel = document.getElementById('detailPanel');
const placeholderPanel = document.getElementById('placeholderPanel');
placeholderPanel.style.display = 'none';
detailPanel.style.display = 'block';
// Highlight selected row
document.querySelectorAll('#tableBody tr').forEach(tr => tr.classList.remove('table-active'));
event.currentTarget && event.currentTarget.classList.add('table-active');
// Reset panels
document.getElementById('detailTitle').textContent = 'Henter...';
document.getElementById('detailCaseBadge').textContent = '';
document.getElementById('detailMeta').innerHTML = '<div class="text-center py-2"><div class="spinner-border spinner-border-sm text-primary"></div></div>';
document.getElementById('modulePills').style.display = 'none';
document.getElementById('contactRow').style.display = 'none';
document.getElementById('kommentarFeed').innerHTML = '';
document.getElementById('detailOpenBtn').href = type === 'case' ? `/sag/${id}` : `/ticket/tickets/${id}`;
window._currentDetailId = id;
window._currentDetailType = type;
try {
const [caseRes, contactsRes, kommentarerRes, modulesRes] = await Promise.all([
fetch(`/api/v1/sag/${id}`),
fetch(`/api/v1/sag/${id}/contacts`),
fetch(`/api/v1/sag/${id}/kommentarer`),
fetch(`/api/v1/sag/${id}/modules`)
]);
const data = caseRes.ok ? await caseRes.json() : null;
const contacts = contactsRes.ok ? await contactsRes.json() : [];
const kommentarer = kommentarerRes.ok ? await kommentarerRes.json() : [];
const modules = modulesRes.ok ? await modulesRes.json() : [];
if (!data) {
document.getElementById('detailMeta').innerHTML = '<div class="alert alert-danger m-0">Kunne ikke hente sag</div>';
return;
}
// Header
document.getElementById('detailTitle').textContent = data.titel || 'Ingen titel';
document.getElementById('detailCaseBadge').textContent = `Sag #${id}`;
// Module pills
if (modules && modules.length > 0) {
const pillsEl = document.getElementById('modulePills');
pillsEl.style.display = 'block';
const moduleIcons = {
contacts: 'bi-person', hardware: 'bi-pc-display', files: 'bi-paperclip',
locations: 'bi-geo-alt', calendar: 'bi-calendar', kommentarer: 'bi-chat',
subscriptions: 'bi-arrow-repeat', 'sale-items': 'bi-cart'
};
pillsEl.innerHTML = '<div class="d-flex flex-wrap gap-1">' +
modules.filter(m => m.count > 0).map(m => `
<span class="badge bg-light text-dark border position-relative"
style="cursor:default;"
data-bs-toggle="tooltip"
data-bs-html="true"
title="${m.label || m.module}: ${m.count} ${m.count === 1 ? 'post' : 'poster'}">
<i class="bi ${moduleIcons[m.module] || 'bi-grid'}"></i>
${m.label || m.module}
<span class="badge bg-primary rounded-pill ms-1">${m.count}</span>
</span>
`).join('') +
'</div>';
// Init tooltips
pillsEl.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => new bootstrap.Tooltip(el));
}
// Contact row med ring/SMS
if (contacts && contacts.length > 0) {
const contactRow = document.getElementById('contactRow');
contactRow.style.display = 'block';
contactRow.innerHTML = contacts.slice(0, 3).map(c => {
const name = [c.first_name, c.last_name].filter(Boolean).join(' ');
const phone = c.mobile || c.phone;
const smsHtml = phone ? `<a href="sms:${phone.replace(/\s/g,'')}" class="btn btn-xs btn-outline-success py-0 px-1" style="font-size:11px;" title="SMS ${phone}"><i class="bi bi-chat-dots"></i></a>` : '';
const callHtml = phone ? `<a href="tel:${phone.replace(/\s/g,'')}" class="btn btn-xs btn-outline-primary py-0 px-1" style="font-size:11px;" title="Ring ${phone}"><i class="bi bi-telephone"></i></a>` : '';
return `
<div class="d-flex justify-content-between align-items-center mb-1">
<div>
<span class="small fw-semibold">${name}</span>
${phone ? `<br><span class="small text-muted">${phone}</span>` : ''}
</div>
<div class="d-flex gap-1">${callHtml}${smsHtml}</div>
</div>
`;
}).join('');
}
// Meta
const statusColor = {'Aktiv':'primary','Afsluttet':'success','Annulleret':'secondary','Venter':'warning'}[data.status] || 'secondary';
document.getElementById('detailMeta').innerHTML = `
<div class="row g-1 small">
<div class="col-6">
<span class="text-muted">Status</span><br>
<span class="badge bg-${statusColor}">${data.status || '-'}</span>
</div>
<div class="col-6">
<span class="text-muted">Deadline</span><br>
<strong>${formatShortDate(data.deadline)}</strong>
</div>
${data.customer_name ? `<div class="col-12 mt-1"><i class="bi bi-building text-muted"></i> ${data.customer_name}</div>` : ''}
${data.description ? `<div class="col-12 mt-1 text-muted" style="font-size:11px;">${data.description.substring(0,120)}${data.description.length>120?'...':''}</div>` : ''}
</div>
`;
// Kommentarer feed
renderKommentarFeed(kommentarer);
} catch (error) {
document.getElementById('detailMeta').innerHTML = '<div class="alert alert-danger m-0 small">Fejl ved hentning</div>';
console.error(error);
}
}
function renderKommentarFeed(kommentarer) {
const feed = document.getElementById('kommentarFeed');
if (!kommentarer || kommentarer.length === 0) {
feed.innerHTML = '<p class="text-muted small">Ingen kommentarer endnu.</p>';
return;
}
feed.innerHTML = kommentarer.map(k => `
<div class="mb-2 p-2 rounded ${k.er_system_besked ? 'bg-light border-start border-info border-3' : 'bg-light'}">
<div class="d-flex justify-content-between">
<span class="small fw-semibold">${k.forfatter || 'System'}</span>
<span class="small text-muted">${formatDate(k.created_at)}</span>
</div>
<div class="small mt-1">${k.indhold || ''}</div>
</div>
`).join('');
feed.scrollTop = feed.scrollHeight;
}
async function postKommentar() {
const id = window._currentDetailId;
if (!id) return;
const textarea = document.getElementById('newKommentar');
const indhold = textarea.value.trim();
if (!indhold) return;
try {
const res = await fetch(`/api/v1/sag/${id}/kommentarer`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ indhold, forfatter: '{{ current_user.username if current_user else "Bruger" }}' })
});
if (!res.ok) throw new Error('Fejl');
textarea.value = '';
// Reload comments
const k = await (await fetch(`/api/v1/sag/${id}/kommentarer`)).json();
renderKommentarFeed(k);
} catch (e) {
alert('Kunne ikke sende kommentar');
}
}
async function registrerTid() {
const id = window._currentDetailId;
if (!id) return;
const minutter = parseInt(document.getElementById('tidMinutter').value);
const beskrivelse = document.getElementById('tidBeskrivelse').value.trim();
const afregning = document.getElementById('tidAfregning').value;
if (!minutter || minutter < 1) { alert('Angiv antal minutter'); return; }
if (!beskrivelse) { alert('Angiv en beskrivelse'); return; }
const hours = +(minutter / 60).toFixed(4);
try {
const res = await fetch('/api/v1/timetracking/entries/internal', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
sag_id: id,
original_hours: hours,
description: beskrivelse,
billing_method: afregning,
user_name: '{{ current_user.username if current_user else "Hub" }}',
worked_date: new Date().toISOString().slice(0, 10)
})
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
alert('Fejl: ' + (err.detail || res.status));
return;
}
document.getElementById('tidMinutter').value = '';
document.getElementById('tidBeskrivelse').value = '';
// Visual feedback
const btn = document.querySelector('[onclick="registrerTid()"]');
btn.innerHTML = '<i class="bi bi-check-circle"></i> Registreret!';
btn.classList.replace('btn-success','btn-outline-success');
setTimeout(() => { btn.innerHTML = '<i class="bi bi-clock-history"></i> Registrer tid'; btn.classList.replace('btn-outline-success','btn-success'); }, 2500);
} catch (e) {
alert('Kunne ikke registrere tid: ' + e.message);
}
}
function closeDetail() {
document.getElementById('detailPanel').style.display = 'none';
document.getElementById('placeholderPanel').style.display = 'block';
document.querySelectorAll('#tableBody tr').forEach(tr => tr.classList.remove('table-active'));
window._currentDetailId = null;
}
</script>
{% endblock %}