2026-06-09 00:41:27 +00:00
<!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" />
2026-06-09 10:41:13 +00:00
< link rel = "stylesheet" href = "/static/css/herman-dashboard.css?v=13" />
2026-06-09 00:41:27 +00:00
< 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" >
2026-06-09 10:41:13 +00:00
< a href = "/" class = "nav-pill nav-pill-herman {% if request.url.path == '/' %}active{% endif %}" data-i18n = "nav.herman" > Herman</ a >
2026-06-09 00:41:27 +00:00
</ div >
< div class = "app-sidebar-group" >
2026-06-09 10:41:13 +00:00
< div class = "app-sidebar-label" data-i18n = "nav.crm" > CRM</ div >
< a href = "/clients" class = "nav-pill nav-pill-clients {% if request.url.path.startswith('/clients') %}active{% endif %}" data-i18n = "nav.clients" > Klanten</ a >
< a href = "/deals" class = "nav-pill nav-pill-deals {% if request.url.path.startswith('/deals') %}active{% endif %}" data-i18n = "nav.deals" > Deals</ a >
< a href = "/products" class = "nav-pill nav-pill-products {% if request.url.path.startswith('/products') %}active{% endif %}" data-i18n = "nav.products" > Producten</ a >
< a href = "/suppliers" class = "nav-pill nav-pill-suppliers {% if request.url.path.startswith('/suppliers') %}active{% endif %}" data-i18n = "nav.suppliers" > Leveranciers</ a >
2026-06-09 00:41:27 +00:00
</ div >
< div class = "app-sidebar-group" >
2026-06-09 10:41:13 +00:00
< div class = "app-sidebar-label" data-i18n = "nav.intel" > Intel</ div >
< a href = "/beurs" class = "nav-pill nav-pill-beurs {% if request.url.path.startswith('/beurs') %}active{% endif %}" data-i18n = "nav.beurs" > Beurs Live</ a >
< a href = "/retail" class = "nav-pill nav-pill-retail {% if request.url.path.startswith('/retail') %}active{% endif %}" data-i18n = "nav.retail" > 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 %}" data-i18n = "nav.browser" > Browser</ a >
< a href = "/documents" class = "nav-pill nav-pill-documents {% if request.url.path.startswith('/documents') %}active{% endif %}" data-i18n = "nav.documents" > Documenten</ a >
< a href = "/projects" class = "nav-pill nav-pill-documents {% if request.url.path.startswith('/projects') %}active{% endif %}" data-i18n = "nav.projects" > Projecten</ a >
2026-06-09 00:41:27 +00:00
</ div >
< div class = "app-sidebar-group" >
2026-06-09 10:41:13 +00:00
< div class = "app-sidebar-label" data-i18n = "nav.hermes" > Hermes</ div >
< a href = "/hermes" class = "nav-pill nav-pill-telegram {% if request.url.path.startswith('/hermes') %}active{% endif %}" data-i18n = "nav.telegram" > Telegram</ a >
2026-06-09 00:41:27 +00:00
</ div >
< div class = "app-sidebar-group" >
2026-06-09 10:41:13 +00:00
< div class = "app-sidebar-label" data-i18n = "nav.agents" > Agents</ div >
< a href = "/agents" class = "nav-pill nav-pill-agents {% if request.url.path.startswith('/agents') %}active{% endif %}" data-i18n = "nav.agents" > Agents</ a >
< a href = "/marketing" class = "nav-pill nav-pill-marketing {% if request.url.path.startswith('/marketing') %}active{% endif %}" data-i18n = "nav.marketing" > Marketing</ a >
< a href = "/herman" class = "nav-pill nav-pill-chat {% if request.url.path.startswith('/herman') %}active{% endif %}" data-i18n = "nav.chat" > Chat</ a >
< a href = "/studio" class = "nav-pill nav-pill-studio {% if request.url.path.startswith('/studio') %}active{% endif %}" data-i18n = "nav.studio" > Studio</ a >
< a href = "/packaging" class = "nav-pill nav-pill-studio {% if request.url.path.startswith('/packaging') %}active{% endif %}" data-i18n = "nav.packaging" > Packaging</ a >
< a href = "/reports" class = "nav-pill nav-pill-reports {% if request.url.path.startswith('/reports') %}active{% endif %}" data-i18n = "nav.reports" > Rapporten</ a >
2026-06-09 00:41:27 +00:00
</ div >
< div class = "app-sidebar-group" >
2026-06-09 10:41:13 +00:00
< div class = "app-sidebar-label" data-i18n = "nav.system" > Systeem</ div >
< a href = "/analytics" class = "nav-pill nav-pill-analytics {% if request.url.path.startswith('/analytics') %}active{% endif %}" data-i18n = "nav.analytics" > Analytics</ a >
< a href = "/voice" class = "nav-pill nav-pill-voice {% if request.url.path.startswith('/voice') %}active{% endif %}" data-i18n = "nav.voice" > Voice</ a >
< a href = "/ops" class = "nav-pill nav-pill-ops {% if request.url.path.startswith('/ops') %}active{% endif %}" data-i18n = "nav.ops" > IT Ops</ a >
< a href = "/settings" class = "nav-pill nav-pill-settings {% if request.url.path.startswith('/settings') %}active{% endif %}" data-i18n = "nav.settings" > Instellingen</ a >
< a href = "http://10.4.7.18:3001/aissa/foodlinkk-command-center" target = "_blank" rel = "noopener" class = "nav-pill nav-pill-settings" data-i18n = "nav.gitea" > Gitea</ a >
2026-06-09 00:41:27 +00:00
</ 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 >
2026-06-09 10:41:13 +00:00
< select class = "form-input" style = "width:auto;min-width:110px;padding:0.2rem 0.5rem;font-size:0.72rem;margin:0 0.5rem"
x-model = "locale" @ change = "setLocale(locale)" :title = "$t ? $t('common.language') : 'Taal'" >
< option value = "nl" > NL</ option >
< option value = "en" > EN</ option >
</ select >
2026-06-09 00:41:27 +00:00
< 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 >
2026-06-09 10:41:13 +00:00
< script src = "/static/js/i18n.js?v=2" ></ script >
< script src = "/static/js/cockpit.js?v=2" ></ script >
< script src = "/static/js/viz-engine.js?v=3" ></ script >
< script src = "/static/js/global-viz.js?v=1" ></ script >
2026-06-09 00:41:27 +00:00
< script src = "/static/js/live-pulse.js?v=2" ></ script >
< script >
function appShell () {
return {
2026-06-09 10:41:13 +00:00
locale : 'nl' ,
2026-06-09 00:41:27 +00:00
drawerOpen : false ,
navOpen : false ,
showInstall : false ,
_deferredPrompt : null ,
clockDate : '—' ,
clockTime : '—' ,
_offset : 0 ,
2026-06-09 10:41:13 +00:00
async initApp () {
if ( window . I18n ) await window . I18n . init ();
this . locale = window . I18n ? window . I18n . locale () : 'nl' ;
2026-06-09 00:41:27 +00:00
this . initClock ();
this . initPwa ();
},
2026-06-09 10:41:13 +00:00
async setLocale ( loc ) {
if ( window . I18n ) await window . I18n . setLocale ( loc );
this . locale = loc ;
},
2026-06-09 00:41:27 +00:00
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 >