159 lines
8.0 KiB
HTML
159 lines
8.0 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="nl">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8" />
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=5" />
|
||
|
|
<meta name="theme-color" content="#0a0e14" />
|
||
|
|
<meta name="mobile-web-app-capable" content="yes" />
|
||
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||
|
|
<meta name="apple-mobile-web-app-title" content="Foodlinkk" />
|
||
|
|
<title>{% block title %}{{ page_title }} · Foodlinkk{% endblock %}</title>
|
||
|
|
<link rel="manifest" href="/static/manifest.json?v=1" />
|
||
|
|
<link rel="icon" href="/static/icons/app-icon.svg" type="image/svg+xml" />
|
||
|
|
<link rel="apple-touch-icon" href="/static/icons/app-icon.svg" />
|
||
|
|
<link rel="stylesheet" href="/static/css/palantir-theme.css" />
|
||
|
|
<link rel="stylesheet" href="/static/css/pulse-theme.css" />
|
||
|
|
<link rel="stylesheet" href="/static/css/topnav-neo.css?v=3" />
|
||
|
|
<link rel="stylesheet" href="/static/css/vertical-tabs.css?v=2" />
|
||
|
|
<link rel="stylesheet" href="/static/css/mobile.css?v=2" />
|
||
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"></script>
|
||
|
|
<script defer src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||
|
|
{% block extra_head %}{% endblock %}
|
||
|
|
</head>
|
||
|
|
<body x-data="appShell()" x-init="initApp()" :class="{ 'nav-open': navOpen, 'drawer-open': drawerOpen }">
|
||
|
|
<div class="app-shell">
|
||
|
|
<aside class="app-sidebar" @click="if($event.target.closest('a')) navOpen=false">
|
||
|
|
<div class="app-sidebar-brand">
|
||
|
|
<p class="brand-title">Foodlinkk</p>
|
||
|
|
<p class="brand-name">Command Center</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="app-sidebar-group">
|
||
|
|
<a href="/" class="nav-pill nav-pill-herman {% if request.url.path == '/' %}active{% endif %}">Herman</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="app-sidebar-group">
|
||
|
|
<div class="app-sidebar-label">CRM</div>
|
||
|
|
<a href="/clients" class="nav-pill nav-pill-clients {% if request.url.path.startswith('/clients') %}active{% endif %}">Clients</a>
|
||
|
|
<a href="/deals" class="nav-pill nav-pill-deals {% if request.url.path.startswith('/deals') %}active{% endif %}">Deals</a>
|
||
|
|
<a href="/products" class="nav-pill nav-pill-products {% if request.url.path.startswith('/products') %}active{% endif %}">Products</a>
|
||
|
|
<a href="/suppliers" class="nav-pill nav-pill-suppliers {% if request.url.path.startswith('/suppliers') %}active{% endif %}">Suppliers</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="app-sidebar-group">
|
||
|
|
<div class="app-sidebar-label">Intel</div>
|
||
|
|
<a href="/beurs" class="nav-pill nav-pill-beurs {% if request.url.path.startswith('/beurs') %}active{% endif %}">Beurs Live</a>
|
||
|
|
<a href="/retail" class="nav-pill nav-pill-retail {% if request.url.path.startswith('/retail') %}active{% endif %}">Retail</a>
|
||
|
|
<a href="/browser" class="nav-pill nav-pill-browser {% if request.url.path.startswith('/browser') or request.url.path.startswith('/monitor') %}active{% endif %}">Browser</a>
|
||
|
|
<a href="/documents" class="nav-pill nav-pill-documents {% if request.url.path.startswith('/documents') %}active{% endif %}">Documents</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="app-sidebar-group">
|
||
|
|
<div class="app-sidebar-label">Hermes</div>
|
||
|
|
<a href="/hermes" class="nav-pill nav-pill-telegram {% if request.url.path.startswith('/hermes') %}active{% endif %}">Telegram</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="app-sidebar-group">
|
||
|
|
<div class="app-sidebar-label">Agents</div>
|
||
|
|
<a href="/agents" class="nav-pill nav-pill-agents {% if request.url.path.startswith('/agents') %}active{% endif %}">Agents</a>
|
||
|
|
<a href="/marketing" class="nav-pill nav-pill-marketing {% if request.url.path.startswith('/marketing') %}active{% endif %}">Marketing</a>
|
||
|
|
<a href="/herman" class="nav-pill nav-pill-chat {% if request.url.path.startswith('/herman') %}active{% endif %}">Chat</a>
|
||
|
|
<a href="/studio" class="nav-pill nav-pill-studio {% if request.url.path.startswith('/studio') %}active{% endif %}">Studio</a>
|
||
|
|
<a href="/packaging" class="nav-pill nav-pill-studio {% if request.url.path.startswith('/packaging') %}active{% endif %}">Packaging</a>
|
||
|
|
<a href="/reports" class="nav-pill nav-pill-reports {% if request.url.path.startswith('/reports') %}active{% endif %}">Reports</a>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="app-sidebar-group">
|
||
|
|
<div class="app-sidebar-label">System</div>
|
||
|
|
<a href="/analytics" class="nav-pill nav-pill-analytics {% if request.url.path.startswith('/analytics') %}active{% endif %}">Analytics</a>
|
||
|
|
<a href="/voice" class="nav-pill nav-pill-voice {% if request.url.path.startswith('/voice') %}active{% endif %}">Voice</a>
|
||
|
|
<a href="/ops" class="nav-pill nav-pill-ops {% if request.url.path.startswith('/ops') %}active{% endif %}">IT Ops</a>
|
||
|
|
<a href="/settings" class="nav-pill nav-pill-settings {% if request.url.path.startswith('/settings') %}active{% endif %}">Settings</a>
|
||
|
|
</div>
|
||
|
|
</aside>
|
||
|
|
|
||
|
|
<div class="mobile-nav-overlay" x-show="navOpen" @click="navOpen=false" x-cloak></div>
|
||
|
|
|
||
|
|
<div class="app-main">
|
||
|
|
<header class="app-topbar">
|
||
|
|
<button type="button" class="mobile-menu-btn" @click="navOpen = !navOpen" aria-label="Menu openen">☰</button>
|
||
|
|
<span class="page-tip">Foodlinkk</span>
|
||
|
|
<div class="app-clock"><strong x-text="clockDate">—</strong> · <span x-text="clockTime">—</span></div>
|
||
|
|
</header>
|
||
|
|
<main class="main-topnav">{% block content %}{% endblock %}</main>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="pwa-install-bar" x-show="showInstall" x-cloak>
|
||
|
|
<p>📱 Installeer Foodlinkk als app op je telefoon</p>
|
||
|
|
<button type="button" class="btn btn-sm btn-pulse-green" @click="installPwa()">Installeren</button>
|
||
|
|
<button type="button" class="btn btn-sm" @click="showInstall=false">Later</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script src="/static/js/cockpit.js"></script>
|
||
|
|
<script src="/static/js/live-pulse.js?v=2"></script>
|
||
|
|
<script>
|
||
|
|
function appShell() {
|
||
|
|
return {
|
||
|
|
drawerOpen: false,
|
||
|
|
navOpen: false,
|
||
|
|
showInstall: false,
|
||
|
|
_deferredPrompt: null,
|
||
|
|
clockDate: '—',
|
||
|
|
clockTime: '—',
|
||
|
|
_offset: 0,
|
||
|
|
initApp() {
|
||
|
|
this.initClock();
|
||
|
|
this.initPwa();
|
||
|
|
},
|
||
|
|
async initClock() {
|
||
|
|
try {
|
||
|
|
const r = await fetch('/api/server-time').then(x => x.json());
|
||
|
|
this._offset = new Date(r.utc).getTime() - Date.now();
|
||
|
|
} catch (e) { this._offset = 0; }
|
||
|
|
const tick = () => {
|
||
|
|
const d = new Date(Date.now() + this._offset);
|
||
|
|
this.clockDate = d.toLocaleDateString('nl-NL', { timeZone: 'Europe/Amsterdam', weekday: 'short', day: 'numeric', month: 'short' });
|
||
|
|
this.clockTime = d.toLocaleTimeString('nl-NL', { timeZone: 'Europe/Amsterdam', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||
|
|
};
|
||
|
|
tick();
|
||
|
|
setInterval(tick, 1000);
|
||
|
|
},
|
||
|
|
initPwa() {
|
||
|
|
if (window.matchMedia('(display-mode: standalone)').matches) return;
|
||
|
|
if (/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||
|
|
this.showInstall = !localStorage.getItem('pwa-dismiss');
|
||
|
|
}
|
||
|
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||
|
|
e.preventDefault();
|
||
|
|
this._deferredPrompt = e;
|
||
|
|
this.showInstall = true;
|
||
|
|
});
|
||
|
|
if ('serviceWorker' in navigator) {
|
||
|
|
navigator.serviceWorker.register('/static/sw.js?v=1').catch(function () {});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
async installPwa() {
|
||
|
|
if (this._deferredPrompt) {
|
||
|
|
this._deferredPrompt.prompt();
|
||
|
|
await this._deferredPrompt.userChoice;
|
||
|
|
this._deferredPrompt = null;
|
||
|
|
this.showInstall = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||
|
|
alert('Tik op Deel ↗ en kies «Zet op beginscherm» om Foodlinkk als app te installeren.');
|
||
|
|
} else {
|
||
|
|
alert('Tik op ⋮ menu → «Toevoegen aan startscherm» of «Installeren».');
|
||
|
|
}
|
||
|
|
this.showInstall = false;
|
||
|
|
localStorage.setItem('pwa-dismiss', '1');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
{% block scripts %}{% endblock %}
|
||
|
|
</body>
|
||
|
|
</html>
|