402 lines
24 KiB
Plaintext
402 lines
24 KiB
Plaintext
<%
|
|
// Current path (res.locals.pfad, set in the auth middleware) drives the active
|
|
// state. The old header had none — you could never tell which section you were
|
|
// in. Copied into a differently named local: re-declaring `pfad` here would
|
|
// shadow the EJS local under `with(locals)`.
|
|
var aktPfad = (typeof pfad !== 'undefined' && pfad) ? String(pfad) : '/';
|
|
|
|
// Bewerbungen owns the dashboard and every single application page.
|
|
var istBewerbungen = (aktPfad === '/' || aktPfad.indexOf('/bewerbung') === 0);
|
|
// Offene Angebote only — "übernommen" lives under Stellensuche, with the rest
|
|
// of the search machinery.
|
|
var istAngebote = (aktPfad === '/jobangebote');
|
|
var istPostfach = (aktPfad.indexOf('/postfach') === 0);
|
|
var istVorlagen = (aktPfad.indexOf('/vorlagen') === 0);
|
|
// The search machinery: how offers get found, what was taken, what is blocked.
|
|
var istSuchprofil = (aktPfad.indexOf('/jobsuche') === 0);
|
|
var istUebernommen = (aktPfad.indexOf('/jobangebote/uebernommen') === 0);
|
|
var istBlacklist = (aktPfad.indexOf('/blacklist') === 0);
|
|
var istStellensuche = istSuchprofil || istUebernommen || istBlacklist;
|
|
|
|
var istEinstellungen = (aktPfad.indexOf('/einstellungen') === 0);
|
|
var istAdmin = (aktPfad.indexOf('/admin') === 0);
|
|
|
|
var angemeldet = (typeof user !== 'undefined' && user);
|
|
var initiale = angemeldet ? String(user.username || '?').charAt(0).toUpperCase() : '?';
|
|
%>
|
|
<!-- The bar carries no surface of its own: it takes the page background, so it
|
|
reads as part of the page rather than as a lid on top of it. Held apart from
|
|
the content only by a hairline and, once scrolled, the faintest shadow.
|
|
Slightly translucent + blurred so content passing underneath stays quiet. -->
|
|
<header id="siteHeader"
|
|
class="sticky top-0 z-50 border-b border-gray-200/80 bg-gray-50/85 backdrop-blur-md transition-shadow duration-300 dark:border-white/5 dark:bg-gray-900/85">
|
|
<div class="container mx-auto px-4">
|
|
<div class="flex h-16 items-center gap-3">
|
|
|
|
<!-- Brand -->
|
|
<a href="/" class="nav-brand group flex shrink-0 items-center gap-2.5 rounded-lg">
|
|
<span class="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-sky-400 to-blue-600 shadow-sm shadow-blue-600/20 ring-1 ring-black/5">
|
|
<svg class="h-5 w-5 text-white" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
|
</svg>
|
|
</span>
|
|
<span class="truncate text-[15px] font-semibold tracking-tight text-gray-900 dark:text-white">
|
|
Bewerbungs-Tracker
|
|
</span>
|
|
</a>
|
|
|
|
<!-- Primary navigation (desktop), right-aligned next to the account
|
|
cluster. Order follows the actual workflow: finden -> bewerben ->
|
|
antworten. -->
|
|
<nav class="ml-auto hidden items-center gap-1 lg:flex" aria-label="Hauptnavigation">
|
|
|
|
<a href="/" class="nav-item <%= istBewerbungen ? 'is-active' : '' %>">
|
|
<span>Bewerbungen</span>
|
|
<span class="nav-rail"></span>
|
|
</a>
|
|
|
|
<a href="/jobangebote" class="nav-item <%= istAngebote ? 'is-active' : '' %>">
|
|
<span>Jobangebote</span>
|
|
<span class="js-badge-angebote nav-badge nav-badge--green hidden">0</span>
|
|
<span class="nav-rail"></span>
|
|
</a>
|
|
|
|
<!-- Stellensuche: everything that produces or filters offers. -->
|
|
<div class="relative">
|
|
<button type="button" data-dd="ddSuche" aria-expanded="false" aria-haspopup="true"
|
|
class="nav-item <%= istStellensuche ? 'is-active' : '' %>">
|
|
<span>Stellensuche</span>
|
|
<svg class="dd-chevron h-3.5 w-3.5 text-slate-500 transition-transform duration-200" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
</svg>
|
|
<span class="nav-rail"></span>
|
|
</button>
|
|
|
|
<div id="ddSuche" class="dd-panel hidden w-80">
|
|
<a href="/jobsuche" class="dd-item <%= istSuchprofil ? 'is-active' : '' %>">
|
|
<svg class="dd-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
</svg>
|
|
<span>
|
|
<span class="dd-title">Suchprofil & Läufe</span>
|
|
<span class="dd-desc">Wo und wann automatisch gesucht wird</span>
|
|
</span>
|
|
</a>
|
|
<a href="/jobangebote/uebernommen" class="dd-item <%= istUebernommen ? 'is-active' : '' %>">
|
|
<svg class="dd-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"></path>
|
|
</svg>
|
|
<span>
|
|
<span class="dd-title">Übernommene Angebote</span>
|
|
<span class="dd-desc">Bereits in eine Bewerbung überführt</span>
|
|
</span>
|
|
</a>
|
|
<a href="/blacklist" class="dd-item <%= istBlacklist ? 'is-active' : '' %>">
|
|
<svg class="dd-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"></path>
|
|
</svg>
|
|
<span>
|
|
<span class="dd-title">Blacklist</span>
|
|
<span class="dd-desc">Taucht in keinem Suchlauf wieder auf</span>
|
|
</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<a href="/postfach" class="nav-item <%= istPostfach ? 'is-active' : '' %>">
|
|
<span>Postfach</span>
|
|
<span class="js-badge-postfach nav-badge nav-badge--red hidden">0</span>
|
|
<span class="nav-rail"></span>
|
|
</a>
|
|
|
|
<a href="/vorlagen" class="nav-item <%= istVorlagen ? 'is-active' : '' %>">
|
|
<span>Vorlagen</span>
|
|
<span class="nav-rail"></span>
|
|
</a>
|
|
</nav>
|
|
|
|
<!-- Right cluster: notifications + account. Below lg the nav is hidden,
|
|
so `ml-auto` keeps this pinned right; from lg the nav already claims
|
|
the free space and the cluster just follows it. -->
|
|
<div class="ml-auto flex items-center gap-1.5 lg:ml-3">
|
|
|
|
<!-- Notifications -->
|
|
<div class="relative">
|
|
<button id="notifBtn" type="button" data-dd="notifPanel" aria-expanded="false"
|
|
class="icon-btn" aria-label="Benachrichtigungen" title="Ungelesene E-Mails">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
|
|
</svg>
|
|
<span id="notifBadge" class="dot-badge dot-badge--red hidden">0</span>
|
|
</button>
|
|
|
|
<div id="notifPanel" class="dd-panel hidden w-80 max-w-[calc(100vw-2rem)] p-0">
|
|
<div class="sticky top-0 flex items-center justify-between rounded-t-xl border-b border-gray-100 bg-white px-4 py-2.5 dark:border-white/5 dark:bg-slate-800">
|
|
<span class="text-sm font-semibold text-gray-800 dark:text-white">Benachrichtigungen</span>
|
|
<button id="notifMarkAll" type="button" class="hidden text-xs font-medium text-blue-600 hover:underline dark:text-sky-400">Alle gelesen</button>
|
|
</div>
|
|
<ul id="notifList" class="max-h-96 divide-y divide-gray-100 overflow-y-auto dark:divide-white/5"></ul>
|
|
<div id="notifEmpty" class="px-4 py-10 text-center text-sm text-gray-400 dark:text-slate-500">
|
|
Keine ungelesenen Nachrichten
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<% if (angemeldet) { %>
|
|
<!-- Account -->
|
|
<div class="relative">
|
|
<button type="button" data-dd="ddUser" aria-expanded="false" aria-haspopup="true"
|
|
class="nav-item !gap-2 !py-1.5 !pl-1.5 !pr-2">
|
|
<span class="grid h-7 w-7 place-items-center rounded-full bg-blue-600/10 text-[11px] font-semibold text-blue-700 ring-1 ring-blue-600/20 dark:bg-white/10 dark:text-white dark:ring-white/15">
|
|
<%= initiale %>
|
|
</span>
|
|
<span class="hidden max-w-[10rem] truncate font-medium md:inline"><%= user.username %></span>
|
|
<svg class="dd-chevron hidden h-3.5 w-3.5 text-slate-500 transition-transform duration-200 md:block" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<div id="ddUser" class="dd-panel hidden w-60">
|
|
<div class="border-b border-gray-100 px-3 pb-2.5 pt-1 dark:border-white/5">
|
|
<p class="truncate text-sm font-semibold text-gray-800 dark:text-white"><%= user.username %></p>
|
|
<p class="text-xs text-gray-500 dark:text-slate-400">
|
|
<%= user.is_admin ? 'Administrator' : 'Benutzer' %>
|
|
</p>
|
|
</div>
|
|
<div class="pt-1">
|
|
<a href="/einstellungen" class="dd-item <%= istEinstellungen ? 'is-active' : '' %>">
|
|
<svg class="dd-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
</svg>
|
|
<span class="dd-title">Einstellungen</span>
|
|
</a>
|
|
<% if (user.is_admin) { %>
|
|
<a href="/admin" class="dd-item <%= istAdmin ? 'is-active' : '' %>">
|
|
<svg class="dd-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a4 4 0 00-3-3.87M9 20H4v-2a4 4 0 013-3.87m6-2a4 4 0 100-8 4 4 0 000 8z"></path>
|
|
</svg>
|
|
<span class="dd-title">Benutzer verwalten</span>
|
|
</a>
|
|
<% } %>
|
|
</div>
|
|
<div class="mt-1 border-t border-gray-100 pt-1 dark:border-white/5">
|
|
<a href="/logout" class="dd-item dd-item--danger">
|
|
<svg class="dd-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path>
|
|
</svg>
|
|
<span class="dd-title">Abmelden</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% } %>
|
|
|
|
<!-- Mobile menu -->
|
|
<button type="button" id="burgerBtn" aria-expanded="false" aria-controls="mobileNav"
|
|
class="icon-btn lg:hidden" aria-label="Menü">
|
|
<svg id="burgerOpen" class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
</svg>
|
|
<svg id="burgerClose" class="hidden h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile navigation: the same structure, spelled out. The old header just
|
|
dropped the labels and left seven unlabelled icons. -->
|
|
<div id="mobileNav" class="hidden border-t border-gray-200/80 bg-gray-50 lg:hidden dark:border-white/5 dark:bg-gray-900">
|
|
<nav class="container mx-auto space-y-4 px-4 py-4" aria-label="Hauptnavigation (mobil)">
|
|
<div class="space-y-0.5">
|
|
<a href="/" class="m-item <%= istBewerbungen ? 'is-active' : '' %>">Bewerbungen</a>
|
|
<a href="/jobangebote" class="m-item <%= istAngebote ? 'is-active' : '' %>">
|
|
Jobangebote
|
|
<span class="js-badge-angebote nav-badge nav-badge--green ml-auto hidden">0</span>
|
|
</a>
|
|
<a href="/postfach" class="m-item <%= istPostfach ? 'is-active' : '' %>">
|
|
Postfach
|
|
<span class="js-badge-postfach nav-badge nav-badge--red ml-auto hidden">0</span>
|
|
</a>
|
|
<a href="/vorlagen" class="m-item <%= istVorlagen ? 'is-active' : '' %>">Vorlagen</a>
|
|
</div>
|
|
|
|
<div class="space-y-0.5">
|
|
<p class="m-label">Stellensuche</p>
|
|
<a href="/jobsuche" class="m-item <%= istSuchprofil ? 'is-active' : '' %>">Suchprofil & Läufe</a>
|
|
<a href="/jobangebote/uebernommen" class="m-item <%= istUebernommen ? 'is-active' : '' %>">Übernommene Angebote</a>
|
|
<a href="/blacklist" class="m-item <%= istBlacklist ? 'is-active' : '' %>">Blacklist</a>
|
|
</div>
|
|
|
|
<% if (angemeldet) { %>
|
|
<div class="space-y-0.5">
|
|
<p class="m-label">Konto</p>
|
|
<p class="px-3 pb-1 text-sm text-gray-900 dark:text-white">
|
|
<%= user.username %>
|
|
<span class="text-gray-500 dark:text-slate-500">· <%= user.is_admin ? 'Administrator' : 'Benutzer' %></span>
|
|
</p>
|
|
<a href="/einstellungen" class="m-item <%= istEinstellungen ? 'is-active' : '' %>">Einstellungen</a>
|
|
<% if (user.is_admin) { %>
|
|
<a href="/admin" class="m-item <%= istAdmin ? 'is-active' : '' %>">Benutzer verwalten</a>
|
|
<% } %>
|
|
<a href="/logout" class="m-item !text-rose-600 hover:!bg-rose-50 dark:!text-rose-300 dark:hover:!bg-rose-500/10">Abmelden</a>
|
|
</div>
|
|
<% } %>
|
|
</nav>
|
|
</div>
|
|
|
|
<script>
|
|
// --- Dropdowns (Stellensuche, Konto, Benachrichtigungen) ---------------
|
|
// One controller for all of them: click toggles, opening one closes the
|
|
// others, Escape and outside-click close, aria-expanded stays truthful.
|
|
(function () {
|
|
var trigger = Array.prototype.slice.call(document.querySelectorAll('[data-dd]'));
|
|
|
|
function panelOf(btn) { return document.getElementById(btn.getAttribute('data-dd')); }
|
|
|
|
function close(btn) {
|
|
var p = panelOf(btn);
|
|
if (!p) return;
|
|
p.classList.add('hidden');
|
|
btn.setAttribute('aria-expanded', 'false');
|
|
var chev = btn.querySelector('.dd-chevron');
|
|
if (chev) chev.classList.remove('rotate-180');
|
|
}
|
|
|
|
function closeAll(ausser) {
|
|
trigger.forEach(function (b) { if (b !== ausser) close(b); });
|
|
}
|
|
|
|
trigger.forEach(function (btn) {
|
|
btn.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
var p = panelOf(btn);
|
|
if (!p) return;
|
|
var offen = !p.classList.contains('hidden');
|
|
closeAll(btn);
|
|
if (offen) { close(btn); return; }
|
|
p.classList.remove('hidden');
|
|
btn.setAttribute('aria-expanded', 'true');
|
|
var chev = btn.querySelector('.dd-chevron');
|
|
if (chev) chev.classList.add('rotate-180');
|
|
btn.dispatchEvent(new CustomEvent('dd:open'));
|
|
});
|
|
});
|
|
|
|
document.addEventListener('click', function (e) {
|
|
trigger.forEach(function (btn) {
|
|
var p = panelOf(btn);
|
|
if (p && !p.classList.contains('hidden') && !p.contains(e.target) && !btn.contains(e.target)) {
|
|
close(btn);
|
|
}
|
|
});
|
|
});
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape') closeAll(null);
|
|
});
|
|
|
|
// --- Mobile menu ---
|
|
var burger = document.getElementById('burgerBtn');
|
|
var mob = document.getElementById('mobileNav');
|
|
if (burger && mob) {
|
|
burger.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
var offen = mob.classList.toggle('hidden');
|
|
burger.setAttribute('aria-expanded', String(!offen));
|
|
document.getElementById('burgerOpen').classList.toggle('hidden', !offen);
|
|
document.getElementById('burgerClose').classList.toggle('hidden', offen);
|
|
});
|
|
}
|
|
|
|
// The bar has no surface of its own, so once content starts sliding
|
|
// underneath it needs the faintest separation — a hairline shadow, not
|
|
// a drop shadow.
|
|
var head = document.getElementById('siteHeader');
|
|
function onScroll() {
|
|
if (!head) return;
|
|
head.classList.toggle('is-scrolled', window.scrollY > 4);
|
|
}
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
onScroll();
|
|
})();
|
|
|
|
// --- Notification bell: unread received e-mails ------------------------
|
|
(function () {
|
|
var btn = document.getElementById('notifBtn');
|
|
var badge = document.getElementById('notifBadge');
|
|
var list = document.getElementById('notifList');
|
|
var empty = document.getElementById('notifEmpty');
|
|
var markAll = document.getElementById('notifMarkAll');
|
|
if (!btn) return;
|
|
|
|
function esc(s) { var d = document.createElement('div'); d.textContent = (s == null ? '' : String(s)); return d.innerHTML; }
|
|
function fmtDate(d) {
|
|
if (!d) return '';
|
|
try {
|
|
var dt = new Date(d), diff = (Date.now() - dt.getTime()) / 1000;
|
|
if (diff < 60) return 'gerade eben';
|
|
if (diff < 3600) return Math.floor(diff / 60) + ' Min.';
|
|
if (diff < 86400) return Math.floor(diff / 3600) + ' Std.';
|
|
return dt.toLocaleDateString('de-DE');
|
|
} catch (e) { return ''; }
|
|
}
|
|
function render(data) {
|
|
var items = (data && data.items) || [], count = (data && data.count) || 0;
|
|
if (count > 0) { badge.textContent = count > 99 ? '99+' : count; badge.classList.remove('hidden'); }
|
|
else { badge.classList.add('hidden'); }
|
|
list.innerHTML = '';
|
|
if (!items.length) { empty.classList.remove('hidden'); markAll.classList.add('hidden'); return; }
|
|
empty.classList.add('hidden'); markAll.classList.remove('hidden');
|
|
items.forEach(function (it) {
|
|
var li = document.createElement('li');
|
|
var a = document.createElement('a');
|
|
a.href = it.url;
|
|
a.className = 'block px-4 py-3 transition-colors hover:bg-gray-50 dark:hover:bg-white/5';
|
|
a.innerHTML =
|
|
'<div class="flex items-center justify-between gap-2">'
|
|
+ '<span class="truncate text-sm font-medium text-gray-800 dark:text-gray-100">' + esc(it.from) + '</span>'
|
|
+ '<span class="shrink-0 text-[11px] text-gray-400">' + esc(fmtDate(it.date)) + '</span>'
|
|
+ '</div>'
|
|
+ '<div class="truncate text-sm text-gray-700 dark:text-gray-200">' + esc(it.subject) + '</div>'
|
|
+ (it.snippet ? '<div class="mt-0.5 truncate text-xs text-gray-500 dark:text-slate-400">' + esc(it.snippet) + '</div>' : '')
|
|
+ (it.kontext ? '<div class="mt-0.5 truncate text-[11px] text-blue-600 dark:text-sky-400">' + esc(it.kontext) + '</div>' : '');
|
|
li.appendChild(a); list.appendChild(li);
|
|
});
|
|
}
|
|
function load() {
|
|
fetch('/api/notifications', { cache: 'no-store' })
|
|
.then(function (r) { return r.json(); }).then(render).catch(function () { /* ignore */ });
|
|
}
|
|
|
|
btn.addEventListener('dd:open', load); // refresh whenever the panel opens
|
|
markAll.addEventListener('click', function (e) {
|
|
e.preventDefault(); e.stopPropagation();
|
|
fetch('/api/emails/mark-all-read', { method: 'POST' })
|
|
.then(function () { render({ count: 0, items: [] }); }).catch(function () { /* ignore */ });
|
|
});
|
|
load();
|
|
setInterval(load, 60000);
|
|
})();
|
|
|
|
// --- Counters: unlinked mail + open job offers -------------------------
|
|
// Class-based, because each count now shows in both the desktop bar and
|
|
// the mobile menu.
|
|
(function () {
|
|
function setzen(selektor, count) {
|
|
if (!(count > 0)) return;
|
|
document.querySelectorAll(selektor).forEach(function (el) {
|
|
el.textContent = count > 99 ? '99+' : count;
|
|
el.classList.remove('hidden');
|
|
});
|
|
}
|
|
fetch('/api/emails/unassigned-count').then(function (r) { return r.json(); })
|
|
.then(function (d) { setzen('.js-badge-postfach', d && d.count); }).catch(function () { /* ignore */ });
|
|
fetch('/jobangebote/anzahl-offen').then(function (r) { return r.json(); })
|
|
.then(function (d) { setzen('.js-badge-angebote', d && d.count); }).catch(function () { /* ignore */ });
|
|
})();
|
|
</script>
|
|
</header>
|