bmc_hub/app/shared/frontend/base.html

531 lines
26 KiB
HTML

<!DOCTYPE html>
<html lang="da" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}BMC Hub{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<style>
:root {
--bg-body: #f8f9fa;
--bg-card: #ffffff;
--text-primary: #2c3e50;
--text-secondary: #6c757d;
--accent: #0f4c75;
--accent-light: #eef2f5;
--border-radius: 12px;
}
[data-bs-theme="dark"] {
--bg-body: #212529;
--bg-card: #2c3034;
--text-primary: #f8f9fa;
--text-secondary: #adb5bd;
--accent: #3d8bfd; /* Lighter blue for dark mode */
--accent-light: #373b3e;
}
body {
background-color: var(--bg-body);
color: var(--text-primary);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
padding-top: 80px;
transition: background-color 0.3s, color 0.3s;
}
.navbar {
background: var(--bg-card);
box-shadow: 0 2px 15px rgba(0,0,0,0.03);
padding: 1rem 0;
border-bottom: 1px solid rgba(0,0,0,0.1);
}
.navbar-brand {
font-weight: 700;
color: var(--accent);
font-size: 1.25rem;
}
.nav-link {
color: var(--text-secondary);
padding: 0.6rem 1.2rem !important;
border-radius: var(--border-radius);
transition: all 0.2s;
font-weight: 500;
margin: 0 0.2rem;
}
.nav-link:hover, .nav-link.active {
background-color: var(--accent-light);
color: var(--accent);
}
.card {
border: none;
border-radius: var(--border-radius);
box-shadow: 0 2px 15px rgba(0,0,0,0.03);
transition: transform 0.2s, background-color 0.3s;
background: var(--bg-card);
}
.card:hover {
transform: translateY(-2px);
}
.stat-card h3 {
font-size: 2rem;
font-weight: 700;
color: var(--accent);
margin-bottom: 0;
}
.stat-card p {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.table {
color: var(--text-primary);
}
.table th {
font-weight: 600;
color: var(--text-secondary);
border-bottom-width: 1px;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background-color: var(--accent);
border-color: var(--accent);
padding: 0.6rem 1.5rem;
border-radius: 8px;
}
.btn-primary:hover {
background-color: #0a3655;
border-color: #0a3655;
}
.header-search {
background: var(--bg-body);
border: 1px solid rgba(0,0,0,0.1);
padding: 0.6rem 1.2rem;
border-radius: 8px;
width: 300px;
color: var(--text-primary);
}
.header-search:focus {
outline: none;
border-color: var(--accent);
}
.dropdown-menu {
border: none;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
border-radius: 12px;
padding: 0.5rem;
background-color: var(--bg-card);
}
.dropdown-item {
border-radius: 8px;
font-size: 0.9rem;
font-weight: 500;
color: var(--text-secondary);
transition: all 0.2s;
}
.dropdown-item:hover {
background-color: var(--accent-light);
color: var(--accent);
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg fixed-top">
<div class="container-fluid px-4">
<a class="navbar-brand d-flex align-items-center" href="/">
<div class="bg-primary text-white rounded p-1 me-2 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px; background-color: var(--accent) !important;">
<i class="bi bi-hdd-network-fill" style="font-size: 16px;"></i>
</div>
BMC Hub
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mx-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-people me-2"></i>CRM
</a>
<ul class="dropdown-menu mt-2">
<li><a class="dropdown-item py-2" href="/customers">Kunder</a></li>
<li><a class="dropdown-item py-2" href="/contacts">Kontakter</a></li>
<li><a class="dropdown-item py-2" href="/vendors">Leverandører</a></li>
<li><a class="dropdown-item py-2" href="#">Leads</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2" href="#">Rapporter</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-headset me-2"></i>Support
</a>
<ul class="dropdown-menu mt-2">
<li><a class="dropdown-item py-2" href="#">Sager</a></li>
<li><a class="dropdown-item py-2" href="#">Ny Sag</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2" href="#">Knowledge Base</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-cart3 me-2"></i>Salg
</a>
<ul class="dropdown-menu mt-2">
<li><a class="dropdown-item py-2" href="#">Tilbud</a></li>
<li><a class="dropdown-item py-2" href="#">Ordre</a></li>
<li><a class="dropdown-item py-2" href="#">Produkter</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2" href="#">Pipeline</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-currency-dollar me-2"></i>Økonomi
</a>
<ul class="dropdown-menu mt-2">
<li><a class="dropdown-item py-2" href="#">Fakturaer</a></li>
<li><a class="dropdown-item py-2" href="#">Abonnementer</a></li>
<li><a class="dropdown-item py-2" href="#">Betalinger</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2" href="#">Rapporter</a></li>
</ul>
</li>
</ul>
<div class="d-flex align-items-center gap-3">
<button class="btn btn-light rounded-circle border-0" id="darkModeToggle" style="background: var(--accent-light); color: var(--accent);">
<i class="bi bi-moon-fill"></i>
</button>
<button class="btn btn-light rounded-circle border-0" style="background: var(--accent-light); color: var(--accent);"><i class="bi bi-bell"></i></button>
<div class="dropdown">
<a href="#" class="d-flex align-items-center text-decoration-none text-dark dropdown-toggle" data-bs-toggle="dropdown">
<img src="https://ui-avatars.com/api/?name=CT&background=0f4c75&color=fff" class="rounded-circle me-2" width="32">
<span class="small fw-bold" style="color: var(--text-primary)">Christian</span>
</a>
<ul class="dropdown-menu dropdown-menu-end mt-2">
<li><a class="dropdown-item py-2" href="#">Profil</a></li>
<li><a class="dropdown-item py-2" href="/settings"><i class="bi bi-gear me-2"></i>Indstillinger</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2 text-danger" href="#">Log ud</a></li>
</ul>
</div>
</div>
</div>
</div>
</nav>
<!-- Global Search Modal (Cmd+K) -->
<div class="modal fade" id="globalSearchModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" style="max-width: 85vw; width: 85vw;">
<div class="modal-content" style="border: none; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); height: 85vh;">
<div class="modal-body p-0 d-flex flex-column" style="height: 100%;">
<div class="p-4 border-bottom" style="background: var(--bg-card);">
<div class="position-relative">
<i class="bi bi-search position-absolute" style="left: 20px; top: 50%; transform: translateY(-50%); font-size: 1.5rem; color: var(--text-secondary);"></i>
<input
type="text"
id="globalSearchInput"
class="form-control form-control-lg ps-5"
placeholder="Søg efter kunder, sager, produkter... (tryk Esc for at lukke)"
style="border: none; background: var(--bg-body); font-size: 1.25rem; padding: 1rem 1rem 1rem 4rem; border-radius: 12px;"
autofocus
>
</div>
<div class="d-flex gap-2 mt-3">
<span class="badge bg-secondary bg-opacity-10 text-secondary">⌘K for at åbne</span>
<span class="badge bg-secondary bg-opacity-10 text-secondary">ESC for at lukke</span>
<span class="badge bg-secondary bg-opacity-10 text-secondary">↑↓ for at navigere</span>
</div>
</div>
<div class="row g-0 flex-grow-1" style="overflow-y: auto;">
<!-- Search Results Area (3/4 width) -->
<div class="col-lg-9 p-4" style="border-right: 1px solid rgba(0,0,0,0.1);">
<div id="searchResults">
<!-- Empty State -->
<div id="emptyState" class="text-center py-5">
<i class="bi bi-search text-muted" style="font-size: 4rem; opacity: 0.3;"></i>
<p class="text-muted mt-3">Begynd at skrive for at søge...</p>
</div>
<!-- CRM Results -->
<div id="crmResults" class="result-section mb-4" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted text-uppercase small fw-bold mb-0">
<i class="bi bi-people me-2"></i>CRM
</h6>
<a href="/crm/workflow" class="btn btn-sm btn-outline-primary">
<i class="bi bi-diagram-3 me-1"></i>Se Workflow
</a>
</div>
<div class="result-items">
<!-- Dynamic results will be inserted here -->
</div>
</div>
<!-- Support Results -->
<div id="supportResults" class="result-section mb-4" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted text-uppercase small fw-bold mb-0">
<i class="bi bi-headset me-2"></i>Support
</h6>
<a href="/support/workflow" class="btn btn-sm btn-outline-primary">
<i class="bi bi-diagram-3 me-1"></i>Se Workflow
</a>
</div>
<div class="result-items">
<!-- Dynamic results will be inserted here -->
</div>
</div>
<!-- Sales Results -->
<div id="salesResults" class="result-section mb-4" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted text-uppercase small fw-bold mb-0">
<i class="bi bi-cart3 me-2"></i>Salg
</h6>
<a href="/sales/workflow" class="btn btn-sm btn-outline-primary">
<i class="bi bi-diagram-3 me-1"></i>Se Workflow
</a>
</div>
<div class="result-items">
<!-- Dynamic results will be inserted here -->
</div>
</div>
<!-- Finance Results -->
<div id="financeResults" class="result-section mb-4" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="text-muted text-uppercase small fw-bold mb-0">
<i class="bi bi-currency-dollar me-2"></i>Økonomi
</h6>
<a href="/finance/workflow" class="btn btn-sm btn-outline-primary">
<i class="bi bi-diagram-3 me-1"></i>Se Workflow
</a>
</div>
<div class="result-items">
<!-- Dynamic results will be inserted here -->
</div>
</div>
</div>
</div>
<!-- Activity Sidebar (1/4 width) -->
<div class="col-lg-3 p-4" style="background: var(--accent-light);">
<h6 class="text-uppercase small fw-bold mb-3" style="color: var(--accent);">
<i class="bi bi-clock-history me-2"></i>Seneste Aktivitet
</h6>
<div id="recentActivity">
<div class="activity-item mb-3 p-2 rounded" style="background: var(--bg-card);">
<div class="d-flex align-items-start">
<i class="bi bi-person-circle text-primary me-2" style="font-size: 1.2rem;"></i>
<div class="flex-grow-1">
<p class="mb-0 small fw-bold">Advokatgruppen A/S</p>
<p class="mb-0 small text-muted">Opdateret for 2 min siden</p>
</div>
</div>
</div>
<div class="activity-item mb-3 p-2 rounded" style="background: var(--bg-card);">
<div class="d-flex align-items-start">
<i class="bi bi-ticket-detailed text-warning me-2" style="font-size: 1.2rem;"></i>
<div class="flex-grow-1">
<p class="mb-0 small fw-bold">Sag #1234 lukket</p>
<p class="mb-0 small text-muted">For 15 min siden</p>
</div>
</div>
</div>
<div class="activity-item mb-3 p-2 rounded" style="background: var(--bg-card);">
<div class="d-flex align-items-start">
<i class="bi bi-receipt text-success me-2" style="font-size: 1.2rem;"></i>
<div class="flex-grow-1">
<p class="mb-0 small fw-bold">Faktura #5678 betalt</p>
<p class="mb-0 small text-muted">I dag kl. 14:30</p>
</div>
</div>
</div>
<div class="activity-item mb-3 p-2 rounded" style="background: var(--bg-card);">
<div class="d-flex align-items-start">
<i class="bi bi-box-seam text-info me-2" style="font-size: 1.2rem;"></i>
<div class="flex-grow-1">
<p class="mb-0 small fw-bold">Ny ordre oprettet</p>
<p class="mb-0 small text-muted">I går kl. 16:45</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid px-4 py-4">
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Dark Mode Toggle Logic
const darkModeToggle = document.getElementById('darkModeToggle');
const htmlElement = document.documentElement;
const icon = darkModeToggle.querySelector('i');
// Check local storage
if (localStorage.getItem('theme') === 'dark') {
htmlElement.setAttribute('data-bs-theme', 'dark');
icon.classList.replace('bi-moon-fill', 'bi-sun-fill');
}
darkModeToggle.addEventListener('click', () => {
if (htmlElement.getAttribute('data-bs-theme') === 'dark') {
htmlElement.setAttribute('data-bs-theme', 'light');
localStorage.setItem('theme', 'light');
icon.classList.replace('bi-sun-fill', 'bi-moon-fill');
} else {
htmlElement.setAttribute('data-bs-theme', 'dark');
localStorage.setItem('theme', 'dark');
icon.classList.replace('bi-moon-fill', 'bi-sun-fill');
}
});
// Global Search Modal (Cmd+K)
const searchModal = new bootstrap.Modal(document.getElementById('globalSearchModal'));
const searchInput = document.getElementById('globalSearchInput');
// Keyboard shortcut: Cmd+K or Ctrl+K
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
searchModal.show();
setTimeout(() => searchInput.focus(), 300);
}
// ESC to close
if (e.key === 'Escape') {
searchModal.hide();
}
});
let searchTimeout;
// Search function
searchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
clearTimeout(searchTimeout);
// Reset empty state text
const emptyState = document.getElementById('emptyState');
emptyState.innerHTML = `
<i class="bi bi-search text-muted" style="font-size: 4rem; opacity: 0.3;"></i>
<p class="text-muted mt-3">Begynd at skrive for at søge...</p>
`;
if (query.length < 2) {
emptyState.style.display = 'block';
document.getElementById('crmResults').style.display = 'none';
document.getElementById('supportResults').style.display = 'none';
if (document.getElementById('salesResults')) document.getElementById('salesResults').style.display = 'none';
if (document.getElementById('financeResults')) document.getElementById('financeResults').style.display = 'none';
return;
}
searchTimeout = setTimeout(async () => {
try {
const response = await fetch(`/api/v1/dashboard/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
emptyState.style.display = 'none';
// CRM Results (Customers + Contacts + Vendors)
const crmSection = document.getElementById('crmResults');
const allResults = [
...(data.customers || []).map(c => ({...c, url: `/customers/${c.id}`, icon: 'building'})),
...(data.contacts || []).map(c => ({...c, url: `/contacts/${c.id}`, icon: 'person'})),
...(data.vendors || []).map(c => ({...c, url: `/vendors/${c.id}`, icon: 'shop'}))
];
if (allResults.length > 0) {
crmSection.style.display = 'block';
crmSection.querySelector('.result-items').innerHTML = allResults.map(item => `
<a href="${item.url}" class="result-item d-block p-3 mb-2 rounded text-decoration-none" style="background: var(--bg-card); border: 1px solid transparent; transition: all 0.2s;">
<div class="d-flex justify-content-between align-items-center">
<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-${item.icon} text-primary"></i>
</div>
<div>
<p class="mb-0 fw-bold" style="color: var(--text-primary);">${item.name}</p>
<p class="mb-0 small text-muted">${item.type} ${item.email ? '• ' + item.email : ''}</p>
</div>
</div>
<i class="bi bi-arrow-right" style="color: var(--accent);"></i>
</div>
</a>
`).join('');
} else {
crmSection.style.display = 'none';
emptyState.style.display = 'block';
emptyState.innerHTML = `
<i class="bi bi-search text-muted" style="font-size: 4rem; opacity: 0.3;"></i>
<p class="text-muted mt-3">Ingen resultater fundet for "${query}"</p>
`;
}
// Hide other sections for now as we don't have real data for them yet
document.getElementById('supportResults').style.display = 'none';
if (document.getElementById('salesResults')) document.getElementById('salesResults').style.display = 'none';
if (document.getElementById('financeResults')) document.getElementById('financeResults').style.display = 'none';
} catch (error) {
console.error('Search error:', error);
}
}, 300); // Debounce 300ms
});
// Hover effects for result items
document.addEventListener('DOMContentLoaded', () => {
const style = document.createElement('style');
style.textContent = `
.result-item:hover {
border-color: var(--accent) !important;
background: var(--accent-light) !important;
}
`;
document.head.appendChild(style);
});
// Reset search when modal is closed
document.getElementById('globalSearchModal').addEventListener('hidden.bs.modal', () => {
searchInput.value = '';
document.getElementById('emptyState').style.display = 'block';
document.getElementById('crmResults').style.display = 'none';
document.getElementById('supportResults').style.display = 'none';
if (document.getElementById('salesResults')) document.getElementById('salesResults').style.display = 'none';
if (document.getElementById('financeResults')) document.getElementById('financeResults').style.display = 'none';
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>