226 lines
10 KiB
HTML
226 lines
10 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
|
|
|
{% block title %}Dashboard - BMC Hub{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between align-items-center mb-5">
|
|
<div>
|
|
<h2 class="fw-bold mb-1">Dashboard</h2>
|
|
<p class="text-muted mb-0">Velkommen tilbage, Christian</p>
|
|
</div>
|
|
<div class="d-flex gap-3">
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search"></i></span>
|
|
<input type="text" id="dashboardSearchInput" class="form-control border-start-0 ps-0" placeholder="Søg i alt... (⌘K)" style="max-width: 250px;" role="button">
|
|
</div>
|
|
<div class="dropdown">
|
|
<button class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
|
<i class="bi bi-plus-lg me-2"></i>Ny Oprettelse
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li><a class="dropdown-item" href="/customers"><i class="bi bi-building me-2"></i>Ny Kunde</a></li>
|
|
<li><a class="dropdown-item" href="/contacts"><i class="bi bi-person me-2"></i>Ny Kontakt</a></li>
|
|
<li><a class="dropdown-item" href="/vendors"><i class="bi bi-shop me-2"></i>Ny Leverandør</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 1. Live Metrics Cards -->
|
|
<div class="row g-4 mb-5">
|
|
<div class="col-md-3">
|
|
<div class="card stat-card p-4 h-100">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<p>Kunder</p>
|
|
<i class="bi bi-building text-primary" style="color: var(--accent) !important;"></i>
|
|
</div>
|
|
<h3 id="customerCount">-</h3>
|
|
<small class="text-success"><i class="bi bi-check-circle"></i> Aktive i systemet</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card p-4 h-100">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<p>Kontakter</p>
|
|
<i class="bi bi-people text-primary" style="color: var(--accent) !important;"></i>
|
|
</div>
|
|
<h3 id="contactCount">-</h3>
|
|
<small class="text-muted">Tilknyttede personer</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card p-4 h-100">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<p>Leverandører</p>
|
|
<i class="bi bi-shop text-primary" style="color: var(--accent) !important;"></i>
|
|
</div>
|
|
<h3 id="vendorCount">-</h3>
|
|
<small class="text-muted">Aktive leverandøraftaler</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card stat-card p-4 h-100">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<p>System Status</p>
|
|
<i class="bi bi-cpu text-primary" style="color: var(--accent) !important;"></i>
|
|
</div>
|
|
<h3 id="systemStatus" class="text-success">Online</h3>
|
|
<small class="text-muted" id="systemVersion">v1.0.0</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-4">
|
|
<!-- 2. Recent Activity (New Customers) -->
|
|
<div class="col-lg-8">
|
|
<div class="card p-4 h-100">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h5 class="fw-bold mb-0">Seneste Tilføjelser</h5>
|
|
<a href="/customers" class="btn btn-sm btn-light">Se alle</a>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>Navn</th>
|
|
<th>Type</th>
|
|
<th>Oprettet</th>
|
|
<th class="text-end">Handling</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="recentActivityTable">
|
|
<tr>
|
|
<td colspan="4" class="text-center py-4">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3. Vendor Distribution & Quick Links -->
|
|
<div class="col-lg-4">
|
|
<div class="card p-4 mb-4">
|
|
<h5 class="fw-bold mb-4">Leverandør Fordeling</h5>
|
|
<div id="vendorDistribution">
|
|
<div class="text-center py-3">
|
|
<div class="spinner-border text-primary" role="status"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 4. Quick Actions / Shortcuts -->
|
|
<div class="card p-4">
|
|
<h5 class="fw-bold mb-3">Genveje</h5>
|
|
<div class="d-grid gap-2">
|
|
<a href="/settings" class="btn btn-light text-start p-3 d-flex align-items-center">
|
|
<div class="bg-white p-2 rounded me-3 shadow-sm">
|
|
<i class="bi bi-gear text-primary"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold">Indstillinger</div>
|
|
<small class="text-muted">Konfigurer systemet</small>
|
|
</div>
|
|
</a>
|
|
<a href="/vendors" class="btn btn-light text-start p-3 d-flex align-items-center">
|
|
<div class="bg-white p-2 rounded me-3 shadow-sm">
|
|
<i class="bi bi-truck text-success"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-bold">Leverandører</div>
|
|
<small class="text-muted">Administrer aftaler</small>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
async function loadDashboardStats() {
|
|
try {
|
|
const response = await fetch('/api/v1/dashboard/stats');
|
|
const data = await response.json();
|
|
|
|
// Update Counts
|
|
document.getElementById('customerCount').textContent = data.counts.customers;
|
|
document.getElementById('contactCount').textContent = data.counts.contacts;
|
|
document.getElementById('vendorCount').textContent = data.counts.vendors;
|
|
|
|
// Update Recent Activity
|
|
const activityTable = document.getElementById('recentActivityTable');
|
|
if (data.recent_activity && data.recent_activity.length > 0) {
|
|
activityTable.innerHTML = data.recent_activity.map(item => `
|
|
<tr>
|
|
<td class="fw-bold">
|
|
<div class="d-flex align-items-center">
|
|
<div class="rounded-circle bg-light d-flex align-items-center justify-content-center me-3" style="width: 32px; height: 32px;">
|
|
<i class="bi bi-building text-primary"></i>
|
|
</div>
|
|
${item.name}
|
|
</div>
|
|
</td>
|
|
<td><span class="badge bg-primary bg-opacity-10 text-primary">Kunde</span></td>
|
|
<td class="text-muted">${new Date(item.created_at).toLocaleDateString('da-DK')}</td>
|
|
<td class="text-end">
|
|
<a href="/customers/${item.id}" class="btn btn-sm btn-light"><i class="bi bi-arrow-right"></i></a>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
} else {
|
|
activityTable.innerHTML = '<tr><td colspan="4" class="text-center text-muted py-4">Ingen nylig aktivitet</td></tr>';
|
|
}
|
|
|
|
// Update Vendor Distribution
|
|
const vendorDist = document.getElementById('vendorDistribution');
|
|
if (data.vendor_distribution && data.vendor_distribution.length > 0) {
|
|
const total = data.counts.vendors;
|
|
vendorDist.innerHTML = data.vendor_distribution.map(cat => {
|
|
const percentage = Math.round((cat.count / total) * 100);
|
|
return `
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between mb-1">
|
|
<span class="small fw-bold">${cat.category || 'Ukendt'}</span>
|
|
<span class="small text-muted">${cat.count}</span>
|
|
</div>
|
|
<div class="progress" style="height: 6px;">
|
|
<div class="progress-bar" role="progressbar" style="width: ${percentage}%" aria-valuenow="${percentage}" aria-valuemin="0" aria-valuemax="100"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
} else {
|
|
vendorDist.innerHTML = '<p class="text-muted text-center">Ingen leverandørdata</p>';
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error loading dashboard stats:', error);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', loadDashboardStats);
|
|
|
|
// Connect dashboard search input to global search modal
|
|
document.getElementById('dashboardSearchInput').addEventListener('click', () => {
|
|
const modalEl = document.getElementById('globalSearchModal');
|
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
modal.show();
|
|
|
|
// Focus input when modal opens
|
|
modalEl.addEventListener('shown.bs.modal', () => {
|
|
document.getElementById('globalSearchInput').focus();
|
|
}, { once: true });
|
|
});
|
|
|
|
// Also handle focus (e.g. via tab navigation)
|
|
document.getElementById('dashboardSearchInput').addEventListener('focus', (e) => {
|
|
e.target.click();
|
|
e.target.blur(); // Remove focus from this input so we don't get stuck in a loop or keep cursor here
|
|
});
|
|
</script>
|
|
{% endblock %}
|