2025-12-13 12:06:28 +01:00
{% extends "shared/frontend/base.html" %}
2025-12-10 18:29:13 +01:00
2025-12-13 12:06:28 +01:00
{% block title %}Timetracking Dashboard - BMC Hub{% endblock %}
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
2025-12-13 12:06:28 +01:00
{% block extra_css %}
< style >
/* Page specific styles */
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
.stat-card {
text-align: center;
padding: 1.5rem;
}
.stat-card h3 {
font-size: 2.5rem;
font-weight: 700;
color: var(--accent);
margin-bottom: 0.5rem;
}
.stat-card p {
color: var(--text-secondary);
font-size: 0.9rem;
margin: 0;
}
.btn-primary {
background-color: var(--accent);
border-color: var(--accent);
padding: 0.6rem 1.5rem;
border-radius: 8px;
}
.badge {
padding: 0.4rem 0.8rem;
border-radius: 6px;
font-weight: 500;
}
.table {
background: var(--bg-card);
border-radius: var(--border-radius);
}
.table th {
font-weight: 600;
color: var(--text-secondary);
font-size: 0.85rem;
text-transform: uppercase;
}
.sync-status {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 8px;
background: var(--accent-light);
color: var(--accent);
font-size: 0.9rem;
}
.spinner-border-sm {
width: 1rem;
height: 1rem;
}
2025-12-13 12:06:28 +01:00
< / style >
{% endblock %}
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
2025-12-13 12:06:28 +01:00
{% block content %}
< div class = "container-fluid py-4" >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
<!-- Header -->
< div class = "row mb-4" >
< div class = "col-12" >
< h1 class = "mb-1" >
< i class = "bi bi-clock-history text-primary" > < / i > Tidsregistrering
< / h1 >
< p class = "text-muted" > Synkroniser, godkend og fakturer tidsregistreringer fra vTiger< / p >
< / div >
< / div >
<!-- Safety Status Banner -->
< div class = "row mb-4" >
< div class = "col-12" >
< div class = "alert alert-info d-flex align-items-center" role = "alert" >
< i class = "bi bi-shield-lock-fill me-2" > < / i >
< div >
< strong > Safety Mode Aktiv< / strong > -
Modulet kører i read-only mode. Ingen ændringer sker i vTiger eller e-conomic.
< small class = "d-block mt-1" >
< span class = "badge bg-success" > vTiger: READ-ONLY< / span >
< span class = "badge bg-success ms-1" > e-conomic: DRY-RUN< / span >
< / small >
< / div >
< / div >
< / div >
< / div >
<!-- Statistics Cards -->
< div class = "row mb-4" >
< div class = "col-md-3" >
< div class = "card stat-card" >
< h3 id = "stat-customers" > -< / h3 >
< p > Kunder med tider< / p >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card stat-card" >
< h3 id = "stat-pending" class = "text-warning" > -< / h3 >
< p > Afventer godkendelse< / p >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card stat-card" >
< h3 id = "stat-approved" class = "text-success" > -< / h3 >
< p > Godkendte< / p >
< / div >
< / div >
< div class = "col-md-3" >
< div class = "card stat-card" >
< h3 id = "stat-hours" > -< / h3 >
< p > Timer godkendt< / p >
< / div >
< / div >
< / div >
<!-- Actions Row -->
< div class = "row mb-4" >
< div class = "col-12" >
< div class = "card" >
< div class = "card-body" >
2025-12-10 18:29:13 +01:00
< div class = "d-flex justify-content-between align-items-center flex-wrap gap-3" >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
< div >
< h5 class = "mb-1" > Synkronisering< / h5 >
< p class = "text-muted mb-0 small" > Hent nye tidsregistreringer fra vTiger< / p >
< / div >
2025-12-10 18:29:13 +01:00
< div class = "d-flex gap-2 align-items-center" >
< div class = "form-check form-switch" >
< input class = "form-check-input" type = "checkbox" id = "hide-time-card"
onchange="loadCustomerStats()" checked>
< label class = "form-check-label" for = "hide-time-card" >
Skjul klippekort-kunder
< / label >
< / div >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
< button class = "btn btn-primary" onclick = "syncFromVTiger()" id = "sync-btn" >
< i class = "bi bi-arrow-repeat" > < / i > Synkroniser
< / button >
< / div >
< / div >
< div id = "sync-status" class = "mt-3 d-none" > < / div >
< / div >
< / div >
< / div >
< / div >
<!-- Customer List -->
< div class = "row" >
< div class = "col-12" >
< div class = "card" >
< div class = "card-header bg-white" >
< h5 class = "mb-0" > Kunder med åbne tidsregistreringer< / h5 >
< / div >
< div class = "card-body" >
< div id = "loading" class = "text-center py-4" >
< div class = "spinner-border text-primary" role = "status" >
< span class = "visually-hidden" > Indlæser...< / span >
< / div >
< / div >
< div id = "customer-table" class = "d-none" >
< table class = "table table-hover" >
< thead >
< tr >
< th > Kunde< / th >
< th > Total Timer< / th >
< th class = "text-center" > Afventer< / th >
< th class = "text-center" > Godkendt< / th >
< th class = "text-center" > Afvist< / th >
< th class = "text-end" > Handlinger< / th >
< / tr >
< / thead >
< tbody id = "customer-tbody" >
< / tbody >
< / table >
< / div >
< div id = "no-data" class = "text-center py-4 d-none" >
< i class = "bi bi-inbox text-muted" style = "font-size: 3rem;" > < / i >
< p class = "text-muted mt-3" > Ingen tidsregistreringer fundet< / p >
< button class = "btn btn-primary" onclick = "syncFromVTiger()" >
< i class = "bi bi-arrow-repeat" > < / i > Synkroniser fra vTiger
< / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< / div >
2025-12-13 12:06:28 +01:00
<!-- Time Entries Modal -->
< div class = "modal fade" id = "timeEntriesModal" tabindex = "-1" >
< div class = "modal-dialog modal-xl" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" >
< i class = "bi bi-clock-history" > < / i > Tidsregistreringer - < span id = "modal-customer-name" > < / span >
< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal" > < / button >
< / div >
< div class = "modal-body" >
< div class = "alert alert-info mb-3" >
< i class = "bi bi-info-circle" > < / i >
< strong > Bemærk:< / strong > Oversigten viser kun < em > fakturabare, ikke-fakturerede< / em > registreringer.
Her kan du se alle registreringer inkl. ikke-fakturabare og allerede fakturerede.
< div class = "mt-2" >
< div class = "form-check form-check-inline" >
< input class = "form-check-input" type = "checkbox" id = "filter-billable" checked onchange = "filterModalEntries()" >
< label class = "form-check-label" for = "filter-billable" > Kun fakturabare< / label >
< / div >
< div class = "form-check form-check-inline" >
< input class = "form-check-input" type = "checkbox" id = "filter-not-invoiced" checked onchange = "filterModalEntries()" >
< label class = "form-check-label" for = "filter-not-invoiced" > Kun ikke-fakturerede< / label >
< / div >
< / div >
< / div >
< div id = "time-entries-loading" class = "text-center py-5" >
< div class = "spinner-border text-primary" role = "status" > < / div >
< p class = "mt-2" > Indlæser tidsregistreringer...< / p >
< / div >
< div id = "time-entries-content" class = "d-none" >
< div class = "table-responsive" >
< table class = "table table-sm" >
< thead >
< tr >
< th > Case< / th >
< th > Dato< / th >
< th > Timer< / th >
< th > Status< / th >
< th > Udført af< / th >
< th > Handlinger< / th >
< / tr >
< / thead >
< tbody id = "time-entries-tbody" > < / tbody >
< / table >
< / div >
< / div >
< div id = "time-entries-empty" class = "alert alert-info d-none" >
Ingen tidsregistreringer fundet for denne kunde
< / div >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary" data-bs-dismiss = "modal" > Luk< / button >
< / div >
< / div >
< / div >
< / div >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
2025-12-13 12:06:28 +01:00
< script >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
// Load customer stats
async function loadCustomerStats() {
try {
2025-12-10 18:29:13 +01:00
// Check if we should hide time card customers
const hideTimeCard = document.getElementById('hide-time-card')?.checked ?? true;
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
const response = await fetch('/api/v1/timetracking/wizard/stats');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const customers = await response.json();
// Valider at vi fik et array
if (!Array.isArray(customers)) {
console.error('Invalid response format:', customers);
throw new Error('Uventet dataformat fra server');
}
// Filtrer kunder uden tidsregistreringer eller kun med godkendte/afviste
2025-12-10 18:29:13 +01:00
let activeCustomers = customers.filter(c =>
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
c.pending_count > 0 || c.approved_count > 0
);
2025-12-10 18:29:13 +01:00
// Filtrer klippekort-kunder hvis toggled
if (hideTimeCard) {
activeCustomers = activeCustomers.filter(c => !c.uses_time_card);
}
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
if (activeCustomers.length === 0) {
document.getElementById('loading').classList.add('d-none');
document.getElementById('no-data').classList.remove('d-none');
return;
}
// Calculate totals (kun aktive kunder)
let totalCustomers = activeCustomers.length;
let totalPending = activeCustomers.reduce((sum, c) => sum + (c.pending_count || 0), 0);
let totalApproved = activeCustomers.reduce((sum, c) => sum + (c.approved_count || 0), 0);
let totalHours = activeCustomers.reduce((sum, c) => sum + (parseFloat(c.total_approved_hours) || 0), 0);
// Update stat cards
document.getElementById('stat-customers').textContent = totalCustomers;
document.getElementById('stat-pending').textContent = totalPending;
document.getElementById('stat-approved').textContent = totalApproved;
document.getElementById('stat-hours').textContent = totalHours.toFixed(1) + 'h';
// Build table (kun aktive kunder)
const tbody = document.getElementById('customer-tbody');
tbody.innerHTML = activeCustomers.map(customer => `
< tr >
< td >
2025-12-10 18:29:13 +01:00
< div class = "d-flex justify-content-between align-items-start" >
< div >
< strong > ${customer.customer_name || 'Ukendt kunde'}< / strong >
< br > < small class = "text-muted" > ${customer.total_entries || 0} registreringer< / small >
< / div >
${customer.uses_time_card ? '< span class = "badge bg-info" > Klippekort< / span > ' : ''}
< / div >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
< / td >
< td > ${parseFloat(customer.total_original_hours || 0).toFixed(1)}h< / td >
< td class = "text-center" >
< span class = "badge bg-warning" > ${customer.pending_count || 0}< / span >
< / td >
< td class = "text-center" >
< span class = "badge bg-success" > ${customer.approved_count || 0}< / span >
< / td >
< td class = "text-center" >
< span class = "badge bg-danger" > ${customer.rejected_count || 0}< / span >
< / td >
< td class = "text-end" >
2025-12-13 12:06:28 +01:00
< button class = "btn btn-sm btn-info me-1"
onclick="viewTimeEntries(${customer.customer_id}, '${(customer.customer_name || 'Ukendt kunde').replace(/'/g, "\\'")}')"
title="Se alle tidsregistreringer">
< i class = "bi bi-clock-history" > < / i >
< / button >
2025-12-10 18:29:13 +01:00
< button class = "btn btn-sm btn-outline-secondary me-1"
onclick="toggleTimeCard(${customer.customer_id}, ${customer.uses_time_card ? 'false' : 'true'})"
title="${customer.uses_time_card ? 'Fjern klippekort' : 'Markér som klippekort'}">
< i class = "bi bi-card-checklist" > < / i >
< / button >
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
${(customer.pending_count || 0) > 0 ? `
< a href = "/timetracking/wizard?customer_id=${customer.customer_id}"
2025-12-10 18:29:13 +01:00
class="btn btn-sm btn-primary me-1">
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
< i class = "bi bi-check-circle" > < / i > Godkend
< / a >
` : ''}
${(customer.approved_count || 0) > 0 ? `
< button class = "btn btn-sm btn-success"
onclick="generateOrder(${customer.customer_id})">
< i class = "bi bi-receipt" > < / i > Opret ordre
< / button >
` : ''}
< / td >
< / tr >
`).join('');
document.getElementById('loading').classList.add('d-none');
document.getElementById('customer-table').classList.remove('d-none');
} catch (error) {
console.error('Error loading stats:', error);
console.error('Error stack:', error.stack);
document.getElementById('loading').innerHTML = `
< div class = "alert alert-danger" >
< i class = "bi bi-exclamation-triangle" > < / i >
< strong > Fejl ved indlæsning:< / strong > ${error.message}
< br > < small class = "text-muted" > Prøv at genindlæse siden med Cmd+Shift+R (Mac) eller Ctrl+Shift+F5 (Windows)< / small >
< / div >
`;
}
}
// Sync from vTiger
async function syncFromVTiger() {
const btn = document.getElementById('sync-btn');
const statusDiv = document.getElementById('sync-status');
btn.disabled = true;
btn.innerHTML = '< span class = "spinner-border spinner-border-sm me-2" > < / span > Synkroniserer...';
statusDiv.classList.remove('d-none');
statusDiv.innerHTML = '< div class = "alert alert-info mb-0" > < i class = "bi bi-hourglass-split" > < / i > Synkronisering i gang...< / div > ';
try {
const response = await fetch('/api/v1/timetracking/sync', {
method: 'POST'
});
const result = await response.json();
statusDiv.innerHTML = `
< div class = "alert alert-success mb-0" >
< strong > < i class = "bi bi-check-circle" > < / i > Synkronisering fuldført!< / strong >
< ul class = "mb-0 mt-2" >
< li > Kunder: ${result.customers_imported || 0} nye, ${result.customers_updated || 0} opdateret< / li >
< li > Cases: ${result.cases_imported || 0} nye, ${result.cases_updated || 0} opdateret< / li >
< li > Tidsregistreringer: ${result.times_imported || 0} nye, ${result.times_updated || 0} opdateret< / li >
< / ul >
${result.duration_seconds ? `< small class = "text-muted d-block mt-2" > Varighed: ${result.duration_seconds.toFixed(1)}s< / small > ` : ''}
< / div >
`;
// Reload data
setTimeout(() => {
location.reload();
}, 2000);
} catch (error) {
statusDiv.innerHTML = `
< div class = "alert alert-danger mb-0" >
< i class = "bi bi-x-circle" > < / i > Synkronisering fejlede: ${error.message}
< / div >
`;
} finally {
btn.disabled = false;
btn.innerHTML = '< i class = "bi bi-arrow-repeat" > < / i > Synkroniser';
}
}
// Generate order for customer
async function generateOrder(customerId) {
if (!confirm('Opret ordre for alle godkendte tidsregistreringer?')) {
return;
}
try {
const response = await fetch(`/api/v1/timetracking/orders/generate/${customerId}`, {
method: 'POST'
});
2025-12-10 18:29:13 +01:00
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Fejl ved oprettelse af ordre');
}
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
const order = await response.json();
2025-12-10 18:29:13 +01:00
// Show success message and redirect to orders page
alert(`✅ Ordre oprettet!\n\nOrdrenummer: ${order.order_number}\nTotal: ${parseFloat(order.total_amount).toFixed(2)} DKK\n\nDu redirectes nu til ordre-siden...`);
// Redirect to orders page instead of reloading
window.location.href = '/timetracking/orders';
} catch (error) {
alert('❌ Fejl ved oprettelse af ordre:\n\n' + error.message);
}
}
// Toggle time card for customer
async function toggleTimeCard(customerId, enabled) {
const action = enabled ? 'markere som klippekort' : 'fjerne klippekort-markering';
if (!confirm(`Er du sikker på at du vil ${action} for denne kunde?`)) {
return;
}
try {
const response = await fetch(`/api/v1/timetracking/customers/${customerId}/time-card?enabled=${enabled}`, {
method: 'PATCH'
});
if (!response.ok) {
throw new Error('Fejl ved opdatering');
}
// Reload customer list
loadCustomerStats();
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
} catch (error) {
2025-12-10 18:29:13 +01:00
alert('❌ Fejl: ' + error.message);
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
}
}
2025-12-13 12:06:28 +01:00
// Global variables for modal filtering
let allModalEntries = [];
let currentModalCustomerId = null;
let currentModalCustomerName = '';
// View time entries for customer
async function viewTimeEntries(customerId, customerName) {
currentModalCustomerId = customerId;
currentModalCustomerName = customerName;
document.getElementById('modal-customer-name').textContent = customerName;
document.getElementById('time-entries-loading').classList.remove('d-none');
document.getElementById('time-entries-content').classList.add('d-none');
document.getElementById('time-entries-empty').classList.add('d-none');
// Reset filters
document.getElementById('filter-billable').checked = true;
document.getElementById('filter-not-invoiced').checked = true;
const modal = new bootstrap.Modal(document.getElementById('timeEntriesModal'));
modal.show();
try {
const response = await fetch(`/api/v1/timetracking/customers/${customerId}/times`);
if (!response.ok) throw new Error('Failed to load time entries');
const data = await response.json();
allModalEntries = data.times || [];
document.getElementById('time-entries-loading').classList.add('d-none');
// Apply filters and render
filterModalEntries();
} catch (error) {
console.error('Error loading time entries:', error);
document.getElementById('time-entries-loading').classList.add('d-none');
showToast('Fejl ved indlæsning af tidsregistreringer', 'danger');
modal.hide();
}
}
// Filter modal entries based on checkboxes
function filterModalEntries() {
const filterBillable = document.getElementById('filter-billable').checked;
const filterNotInvoiced = document.getElementById('filter-not-invoiced').checked;
let filteredEntries = allModalEntries;
if (filterBillable) {
filteredEntries = filteredEntries.filter(e => e.billable !== false);
}
if (filterNotInvoiced) {
filteredEntries = filteredEntries.filter(e => {
const invoiced = e.vtiger_data?.cf_timelog_invoiced;
return invoiced === '0' || invoiced === 0 || invoiced === null;
});
}
if (filteredEntries.length === 0) {
document.getElementById('time-entries-content').classList.add('d-none');
document.getElementById('time-entries-empty').classList.remove('d-none');
return;
}
document.getElementById('time-entries-empty').classList.add('d-none');
document.getElementById('time-entries-content').classList.remove('d-none');
renderModalEntries(filteredEntries);
}
// Render entries in modal table
function renderModalEntries(entries) {
const tbody = document.getElementById('time-entries-tbody');
tbody.innerHTML = entries.map(entry => {
const date = new Date(entry.worked_date).toLocaleDateString('da-DK');
const statusBadge = {
'pending': '< span class = "badge bg-warning" > Afventer< / span > ',
'approved': '< span class = "badge bg-success" > Godkendt< / span > ',
'rejected': '< span class = "badge bg-danger" > Afvist< / span > ',
'billed': '< span class = "badge bg-info" > Faktureret< / span > '
}[entry.status] || entry.status;
// Build case link
let caseLink = entry.case_title || 'Ingen case';
if (entry.case_vtiger_id) {
const recordId = entry.case_vtiger_id.split('x')[1];
const vtigerUrl = `https://bmcnetworks.od2.vtiger.com/view/detail?module=Cases&id=${recordId}&viewtype=summary`;
caseLink = `< a href = "${vtigerUrl}" target = "_blank" class = "text-decoration-none" >
${entry.case_title || 'Case'} < i class = "bi bi-box-arrow-up-right" > < / i >
< / a > `;
}
// Billable and invoiced badges
const invoiced = entry.vtiger_data?.cf_timelog_invoiced;
const badges = [];
if (entry.billable === false) {
badges.push('< span class = "badge bg-secondary" > Ikke fakturerbar< / span > ');
}
if (invoiced === '1' || invoiced === 1) {
badges.push('< span class = "badge bg-dark" > Faktureret i vTiger< / span > ');
}
return `
< tr >
< td >
${caseLink}
${badges.length > 0 ? '< br > ' + badges.join(' ') : ''}
< / td >
< td > ${date}< / td >
< td >
< strong > ${entry.original_hours}t< / strong >
${entry.approved_hours & & entry.status === 'approved' ? `
< br > < small class = "text-muted" >
Oprundet: < strong > ${entry.approved_hours}t< / strong >
${entry.rounded_to ? ` (${entry.rounded_to}t)` : ''}
< / small >
` : ''}
< / td >
< td > ${statusBadge}< / td >
< td > ${entry.user_name || 'Ukendt'}< / td >
< td >
${entry.status === 'pending' ? `
< a href = "/timetracking/wizard?customer_id=${currentModalCustomerId}&time_id=${entry.id}" class = "btn btn-sm btn-success" >
< i class = "bi bi-check" > < / i > Godkend
< / a >
` : ''}
${entry.status === 'approved' & & !entry.billed ? `
< button class = "btn btn-sm btn-outline-danger" onclick = "resetTimeEntry(${entry.id}, ${currentModalCustomerId}, '${currentModalCustomerName.replace(/'/g, " \ \ ' " ) } ' ) " >
< i class = "bi bi-arrow-counterclockwise" > < / i > Nulstil
< / button >
` : ''}
< / td >
< / tr >
`;
}).join('');
}
// Approve time entry
async function approveTimeEntry(timeId, customerId, customerName) {
if (!confirm('Godkend denne tidsregistrering?')) return;
try {
const response = await fetch(`/api/v1/timetracking/wizard/approve/${timeId}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
if (!response.ok) throw new Error('Failed to approve');
showToast('✅ Tidsregistrering godkendt', 'success');
// Reload modal content
viewTimeEntries(customerId, customerName);
// Reload stats
loadCustomerStats();
} catch (error) {
console.error('Error approving:', error);
showToast('Fejl ved godkendelse', 'danger');
}
}
// Reset time entry back to pending
async function resetTimeEntry(timeId, customerId, customerName) {
if (!confirm('Nulstil denne tidsregistrering tilbage til pending?\n\nDen vil blive sat tilbage i godkendelses-køen.')) return;
try {
const response = await fetch(`/api/v1/timetracking/wizard/reset/${timeId}?reason=${encodeURIComponent('Reset til pending')}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to reset');
}
showToast('✅ Tidsregistrering nulstillet til pending', 'success');
// Reload modal content
viewTimeEntries(customerId, customerName);
// Reload stats
loadCustomerStats();
} catch (error) {
console.error('Error resetting:', error);
showToast('Fejl ved nulstilling', 'danger');
}
}
// Toast notification
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `alert alert-${type} position-fixed top-0 end-0 m-3`;
toast.style.zIndex = 9999;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
feat(timetracking): Implement time tracking module with frontend views, HTML templates, and database migrations
- Added FastAPI router for time tracking views including dashboard, wizard, and orders.
- Created HTML templates for the time tracking wizard with responsive design and Bootstrap integration.
- Developed SQL migration script for the time tracking module, including tables for customers, cases, time entries, orders, and audit logs.
- Introduced a script to list all registered routes, focusing on time tracking routes.
- Added test script to verify route registration and specifically check for time tracking routes.
2025-12-09 22:46:30 +01:00
// Load data on page load
loadCustomerStats();
< / script >
2025-12-13 12:06:28 +01:00
< / div >
{% endblock %}