bmc_hub/app/timetracking/frontend/registrations.html

274 lines
10 KiB
HTML
Raw Permalink Normal View History

{% extends "shared/frontend/base.html" %}
{% block title %}Tidsregistreringer - BMC Hub{% endblock %}
{% block extra_css %}
<style>
/* Clean Filters */
.filter-card {
background: white;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
}
/* Table Styling */
.registrations-table {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
overflow: hidden;
border: 1px solid #e2e8f0;
}
.registrations-table th {
background-color: #f8f9fa;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.5px;
padding: 1rem;
border-bottom: 2px solid #e9ecef;
color: #64748b;
}
.registrations-table td {
vertical-align: middle;
padding: 1rem;
border-bottom: 1px solid #f1f5f9;
font-size: 0.9rem;
}
.registrations-table tr:hover {
background-color: #f8fafc;
}
.status-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 4px;
text-transform: uppercase;
font-weight: 600;
}
.status-pending { background: #fff3cd; color: #856404; }
.status-approved { background: #d1e7dd; color: #0f5132; }
.status-rejected { background: #f8d7da; color: #842029; }
.status-billed { background: #cfe2ff; color: #084298; }
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
{% endblock %}
{% block content %}
<div class="container-fluid py-4 px-4 m-0" style="max-width: 1600px; margin: 0 auto;">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="mb-1">Tidsregistreringer</h1>
<p class="text-muted mb-0">Søg og filtrer i alle registreringer</p>
</div>
<button class="btn btn-outline-primary" onclick="loadData()">
<i class="bi bi-arrow-clockwise"></i> Opdater
</button>
</div>
<!-- Filters -->
<div class="filter-card mb-4">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label small text-muted text-uppercase fw-bold">Søgning</label>
<div class="input-group">
<span class="input-group-text bg-white"><i class="bi bi-search"></i></span>
<input type="text" id="filter-search" class="form-control" placeholder="Kunde, beskrivelse, case..." onkeyup="debounceLoad()">
</div>
</div>
<div class="col-md-2">
<label class="form-label small text-muted text-uppercase fw-bold">Status</label>
<select id="filter-status" class="form-select" onchange="loadData()">
<option value="">Alle</option>
<option value="pending">Afventer</option>
<option value="approved">Godkendt</option>
<option value="billed">Faktureret</option>
<option value="rejected">Afvist</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label small text-muted text-uppercase fw-bold">Tekniker</label>
<input type="text" id="filter-user" class="form-control" placeholder="Navn..." onkeyup="debounceLoad()">
</div>
</div>
</div>
<!-- Table -->
<div class="registrations-table">
<div class="table-responsive">
<table class="table mb-0">
<thead>
<tr>
<th>Dato</th>
<th style="width: 20%;">Kunde</th>
<th style="width: 25%;">Beskrivelse / Case</th>
<th>Tekniker</th>
<th class="text-center">Timer</th>
<th class="text-center">Fakt.</th>
<th>Status</th>
<th class="text-end">Handlinger</th>
</tr>
</thead>
<tbody id="table-body">
<tr><td colspan="8" class="text-center py-5 text-muted">Henter data...</td></tr>
</tbody>
</table>
</div>
<!-- Pagination logic could go here -->
</div>
</div>
<!-- Details Modal -->
<div class="modal fade" id="detailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Detaljer</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="details-content">
<!-- Content -->
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
let debounceTimer;
document.addEventListener('DOMContentLoaded', () => {
loadData();
});
function debounceLoad() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(loadData, 500);
}
async function loadData() {
const tbody = document.getElementById('table-body');
const search = document.getElementById('filter-search').value;
const status = document.getElementById('filter-status').value;
const user = document.getElementById('filter-user').value;
// Build URL
const params = new URLSearchParams({
limit: 100, // Hardcoded limit for now
offset: 0
});
if (search) params.append('search', search);
if (status) params.append('status', status);
if (user) params.append('user_name', user);
try {
const response = await fetch(`/api/v1/timetracking/times?${params.toString()}`);
const data = await response.json();
if (data.times.length === 0) {
tbody.innerHTML = `<tr><td colspan="8" class="text-center py-5">Ingen resultater fundet</td></tr>`;
return;
}
tbody.innerHTML = data.times.map(t => `
<tr>
<td>
<div class="fw-bold">${formatDate(t.worked_date)}</div>
<div class="small text-muted">${t.created_at ? new Date(t.created_at).toLocaleTimeString().slice(0,5) : ''}</div>
</td>
<td>
<div class="fw-bold text-dark">${t.customer_name || 'Ukendt'}</div>
</td>
<td>
<div class="small fw-bold text-primary mb-1">
${t.case_vtiger_id ? `<a href="https://bmcnetworks.od2.vtiger.com/index.php?module=HelpDesk&view=Detail&record=${t.case_vtiger_id.replace('39x','')}" target="_blank">${t.case_title || 'Ingen Case'}</a>` : (t.case_title || 'Ingen Case')}
</div>
<div class="text-secondary small" style="max-height: 3em; overflow: hidden; text-overflow: ellipsis;">
${t.description || '-'}
</div>
</td>
<td>
${t.user_name || '-'}
</td>
<td class="text-center">
<span class="badge bg-light text-dark border">${parseFloat(t.original_hours).toFixed(2)}</span>
</td>
<td class="text-center">
${t.billable ? '<i class="bi bi-check-circle-fill text-success"></i>' : '<i class="bi bi-dash-circle text-muted"></i>'}
</td>
<td>
<span class="status-badge status-${t.status}">${getStatusLabel(t.status)}</span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-secondary" onclick="showDetails(${t.id})">
<i class="bi bi-eye"></i>
</button>
<a href="/timetracking/wizard2?customer_id=${t.customer_id}&time_id=${t.id}" class="btn btn-sm btn-outline-primary" title="Gå til godkendelse">
<i class="bi bi-arrow-right"></i>
</a>
</td>
</tr>
`).join('');
} catch (error) {
console.error(error);
tbody.innerHTML = `<tr><td colspan="8" class="text-center py-5 text-danger">Fejl ved hentning af data</td></tr>`;
}
}
function formatDate(dateStr) {
if (!dateStr) return '';
return new Date(dateStr).toLocaleDateString('da-DK');
}
function getStatusLabel(status) {
const labels = {
'pending': 'Afventer',
'approved': 'Godkendt',
'rejected': 'Afvist',
'billed': 'Faktureret'
};
return labels[status] || status;
}
async function showDetails(id) {
const modal = new bootstrap.Modal(document.getElementById('detailsModal'));
const content = document.getElementById('details-content');
content.innerHTML = '<div class="text-center"><div class="spinner-border"></div></div>';
modal.show();
try {
const res = await fetch(`/api/v1/timetracking/times/${id}`);
const t = await res.json();
content.innerHTML = `
<table class="table table-bordered">
<tr><th>ID</th><td>${t.id}</td></tr>
<tr><th>Kunde</th><td>${t.customer_name}</td></tr>
<tr><th>Case</th><td>${t.case_title}</td></tr>
<tr><th>Beskrivelse</th><td>${t.description}</td></tr>
<tr><th>Timer</th><td>${t.original_hours}</td></tr>
<tr><th>Status</th><td>${t.status}</td></tr>
<tr><th>Raw Data</th><td><pre class="bg-light p-2 small">${JSON.stringify(t, null, 2)}</pre></td></tr>
</table>
`;
} catch (e) {
content.innerHTML = `<div class="alert alert-danger">Fejl: ${e.message}</div>`;
}
}
</script>
{% endblock %}