Neue Station zwischen Vorstellungsgespraech und Einstellung - in der
Statusliste, der Gruppierung auf der Startseite, den Farbskalen (Lime,
zwischen dem Gelb des Gespraechs und dem Gruen der Einstellung), im
PDF-Export, in der Statistik und in der REST-API/OpenAPI.
Der Statistik-Funnel zaehlte bisher ever('Vorstellungsgespraech') +
ever('Einstellung') und damit eine Bewerbung, die beide Stationen
durchlaufen hat, doppelt. Mit einer weiteren Stufe waere das schlimmer
geworden, deshalb zaehlt der Funnel jetzt pro Bewerbung statt pro
Statuszeile: jede Stufe zaehlt eine Bewerbung genau einmal, ein spaeterer
Status impliziert die frueheren.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
347 lines
22 KiB
Plaintext
347 lines
22 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<%- include('partials/head') %>
|
|
<style>
|
|
/* ── Statistics page — scoped chart primitives ─────────────────────────
|
|
The cards themselves are plain Tailwind; these classes build the bars,
|
|
the trend and the funnel so the markup stays readable. Everything keys
|
|
off --stat-accent so a single declaration re-tints a row. */
|
|
:root { --stat-accent: #2563eb; }
|
|
.stat-empty {
|
|
padding: 2.5rem 1rem;
|
|
text-align: center;
|
|
color: rgb(100 116 139);
|
|
}
|
|
.dark .stat-empty { color: rgb(148 163 184); }
|
|
|
|
/* KPI tile */
|
|
.kpi {
|
|
position: relative;
|
|
overflow: hidden;
|
|
border-radius: 1rem;
|
|
background: #fff;
|
|
border: 1px solid rgb(226 232 240);
|
|
box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
|
|
padding: 1.25rem 1.25rem 1.1rem;
|
|
transition: transform .18s ease, box-shadow .18s ease;
|
|
}
|
|
.kpi:hover { transform: translateY(-2px); box-shadow: 0 12px 24px -12px rgba(15,23,42,.18); }
|
|
.dark .kpi { background: rgb(30 41 59); border-color: rgba(255,255,255,.06); }
|
|
.kpi-ico {
|
|
display: grid; place-items: center;
|
|
width: 2.5rem; height: 2.5rem; border-radius: .75rem;
|
|
color: #fff; margin-bottom: .9rem;
|
|
}
|
|
.kpi-val { font-size: 1.875rem; font-weight: 700; letter-spacing: -.02em; line-height: 1.1; }
|
|
.kpi-lab { margin-top: .25rem; font-size: .8rem; color: rgb(100 116 139); }
|
|
.dark .kpi-lab { color: rgb(148 163 184); }
|
|
.kpi-sub { margin-top: .35rem; font-size: .72rem; font-weight: 500; }
|
|
.kpi-glow {
|
|
position: absolute; right: -1.5rem; top: -1.5rem;
|
|
width: 5rem; height: 5rem; border-radius: 9999px;
|
|
filter: blur(28px); opacity: .35;
|
|
}
|
|
|
|
/* Generic card */
|
|
.stat-card {
|
|
border-radius: 1rem;
|
|
background: #fff;
|
|
border: 1px solid rgb(226 232 240);
|
|
box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
|
|
padding: 1.25rem 1.5rem 1.5rem;
|
|
}
|
|
.dark .stat-card { background: rgb(30 41 59); border-color: rgba(255,255,255,.06); }
|
|
.stat-card h2 { font-size: 1rem; font-weight: 600; }
|
|
.stat-card .sub { font-size: .78rem; color: rgb(100 116 139); margin-top: .15rem; }
|
|
.dark .stat-card .sub { color: rgb(148 163 184); }
|
|
|
|
/* Horizontal bars (Art / Status / Ort) */
|
|
.bar-row { display: grid; grid-template-columns: minmax(0,9rem) 1fr 2.5rem; align-items: center; gap: .75rem; padding: .35rem 0; }
|
|
.bar-label { font-size: .82rem; color: rgb(71 85 105); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.dark .bar-label { color: rgb(203 213 225); }
|
|
.bar-track { position: relative; height: .6rem; border-radius: 9999px; background: rgb(241 245 249); overflow: hidden; }
|
|
.dark .bar-track { background: rgba(255,255,255,.07); }
|
|
.bar-fill { position: absolute; inset: 0 auto 0 0; border-radius: 9999px; background: var(--stat-accent); width: 0; animation: bar-grow .6s cubic-bezier(.2,.8,.2,1) forwards; }
|
|
.bar-value { font-size: .8rem; font-weight: 600; text-align: right; color: rgb(30 41 52); }
|
|
.dark .bar-value { color: #fff; }
|
|
@keyframes bar-grow { from { width: 0; } }
|
|
|
|
/* Status swatch */
|
|
.swatch { display: inline-block; width: .55rem; height: .55rem; border-radius: 9999px; margin-right: .4rem; background: var(--stat-accent); vertical-align: middle; }
|
|
|
|
/* Trend (monthly) — SVG fills its box, bars keep aspect via viewBox */
|
|
.trend-wrap { width: 100%; }
|
|
.trend-wrap svg { width: 100%; height: auto; display: block; }
|
|
.trend-bar { transition: opacity .15s ease; }
|
|
.trend-bar:hover { opacity: .85; }
|
|
.trend-axis { fill: rgb(148 163 184); font-size: 10px; }
|
|
.dark .trend-axis { fill: rgb(100 116 139); }
|
|
.trend-grid { stroke: rgb(226 232 240); stroke-width: 1; }
|
|
.dark .trend-grid { stroke: rgba(255,255,255,.07); }
|
|
|
|
/* Funnel */
|
|
.funnel-row { margin-bottom: .65rem; }
|
|
.funnel-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: .25rem; }
|
|
.funnel-label { font-size: .82rem; font-weight: 500; color: rgb(51 65 85); }
|
|
.dark .funnel-label { color: rgb(203 213 225); }
|
|
.funnel-meta { font-size: .75rem; color: rgb(100 116 139); }
|
|
.dark .funnel-meta { color: rgb(148 163 184); }
|
|
.funnel-track { position: relative; height: 1.85rem; border-radius: .6rem; background: rgb(241 245 249); overflow: hidden; }
|
|
.dark .funnel-track { background: rgba(255,255,255,.06); }
|
|
.funnel-fill { position: absolute; inset: 0 auto 0 0; border-radius: .6rem; display: grid; place-items: end; padding-right: .65rem; font-size: .8rem; font-weight: 700; color: #fff; width: 0; animation: bar-grow .7s cubic-bezier(.2,.8,.2,1) forwards; }
|
|
.funnel-fill span { transform: translateY(-1px); }
|
|
|
|
/* Activity timeline */
|
|
.tl { position: relative; padding-left: 1.5rem; }
|
|
.tl::before { content: ''; position: absolute; left: .4rem; top: .35rem; bottom: .35rem; width: 2px; background: rgb(226 232 240); }
|
|
.dark .tl::before { background: rgba(255,255,255,.1); }
|
|
.tl-item { position: relative; padding: .35rem 0 .9rem; }
|
|
.tl-item::before { content: ''; position: absolute; left: -1.15rem; top: .55rem; width: .65rem; height: .65rem; border-radius: 9999px; background: var(--stat-accent); box-shadow: 0 0 0 3px #fff; }
|
|
.dark .tl-item::before { box-shadow: 0 0 0 3px rgb(30 41 59); }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.bar-fill, .funnel-fill { animation: none; width: var(--w); }
|
|
.kpi:hover { transform: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen flex flex-col transition-colors duration-300 bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
|
<%- include('partials/header') %>
|
|
|
|
<main class="flex-1 container mx-auto px-4 py-8">
|
|
<!-- Page heading -->
|
|
<div class="mb-8 flex flex-wrap items-end justify-between gap-4">
|
|
<div>
|
|
<h1 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Statistik</h1>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-slate-400">Auswertung all deiner Bewerbungen — Verlauf, Erfolgsquote und Verteilungen.</p>
|
|
</div>
|
|
<div class="flex items-center gap-2 rounded-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-white/5 px-4 py-2 text-sm shadow-sm">
|
|
<svg class="h-4 w-4 text-blue-600 dark:text-sky-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 19V6l12-3v13M9 19c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3zm12-3c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3 3 1.34 3 3z"></path></svg>
|
|
<span class="font-medium text-gray-700 dark:text-gray-200"><%= total %> Bewerbungen</span>
|
|
</div>
|
|
</div>
|
|
|
|
<% if (total === 0) { %>
|
|
<div class="stat-card">
|
|
<div class="stat-empty">
|
|
<svg class="mx-auto mb-3 h-10 w-10 text-gray-300 dark:text-gray-600" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3 13h2l3 9 4-16 3 9h6"></path></svg>
|
|
<p class="text-base font-medium text-gray-600 dark:text-gray-300">Noch keine Bewerbungen erfasst</p>
|
|
<p class="mt-1 text-sm">Sobald du Bewerbungen anlegst, erscheint hier deine komplette Auswertung.</p>
|
|
</div>
|
|
</div>
|
|
<% } else { %>
|
|
|
|
<!-- KPI cards -->
|
|
<div class="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-4 mb-8">
|
|
<div class="kpi">
|
|
<div class="kpi-glow" style="background:#2563eb"></div>
|
|
<div class="kpi-ico" style="background:linear-gradient(135deg,#3b82f6,#2563eb)">
|
|
<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="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>
|
|
</div>
|
|
<div class="kpi-val text-gray-900 dark:text-white"><%= total %></div>
|
|
<div class="kpi-lab">Bewerbungen gesamt</div>
|
|
</div>
|
|
|
|
<div class="kpi">
|
|
<div class="kpi-glow" style="background:#0ea5e9"></div>
|
|
<div class="kpi-ico" style="background:linear-gradient(135deg,#38bdf8,#0ea5e9)">
|
|
<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="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
|
|
</div>
|
|
<div class="kpi-val text-gray-900 dark:text-white"><%= kpis.gesendet %></div>
|
|
<div class="kpi-lab">Versendet</div>
|
|
</div>
|
|
|
|
<div class="kpi">
|
|
<div class="kpi-glow" style="background:#10b981"></div>
|
|
<div class="kpi-ico" style="background:linear-gradient(135deg,#34d399,#10b981)">
|
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
</div>
|
|
<div class="kpi-val text-emerald-600 dark:text-emerald-400"><%= kpis.eingestellt %></div>
|
|
<div class="kpi-lab">Einstellungen</div>
|
|
<div class="kpi-sub text-emerald-600/80 dark:text-emerald-400/80"><%= kpis.erfolgsquote %>% Erfolgsquote</div>
|
|
</div>
|
|
|
|
<div class="kpi">
|
|
<div class="kpi-glow" style="background:#8b5cf6"></div>
|
|
<div class="kpi-ico" style="background:linear-gradient(135deg,#a78bfa,#8b5cf6)">
|
|
<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="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>
|
|
</div>
|
|
<div class="kpi-val text-violet-600 dark:text-violet-300"><%= kpis.gespraech %></div>
|
|
<div class="kpi-lab">Vorstellungsgespräche</div>
|
|
</div>
|
|
|
|
<div class="kpi">
|
|
<div class="kpi-glow" style="background:#f43f5e"></div>
|
|
<div class="kpi-ico" style="background:linear-gradient(135deg,#fb7185,#f43f5e)">
|
|
<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="M6 18L18 6M6 6l12 12"></path></svg>
|
|
</div>
|
|
<div class="kpi-val text-rose-600 dark:text-rose-400"><%= kpis.absagen %></div>
|
|
<div class="kpi-lab">Absagen</div>
|
|
<div class="kpi-sub text-gray-500 dark:text-slate-400"><%= kpis.antwortquote %>% Antwortquote</div>
|
|
</div>
|
|
|
|
<div class="kpi">
|
|
<div class="kpi-glow" style="background:#f59e0b"></div>
|
|
<div class="kpi-ico" style="background:linear-gradient(135deg,#fbbf24,#f59e0b)">
|
|
<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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
</div>
|
|
<div class="kpi-val text-gray-900 dark:text-white"><%= kpis.avgProMonat %></div>
|
|
<div class="kpi-lab">Ø pro Monat</div>
|
|
<div class="kpi-sub text-gray-500 dark:text-slate-400">über <%= kpis.monateAktiv %> Monate</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Monthly trend -->
|
|
<div class="stat-card mb-8">
|
|
<h2 class="text-gray-900 dark:text-white">Bewerbungen pro Monat</h2>
|
|
<p class="sub mb-5">Wie viele Bewerbungen in jedem Monat erstellt wurden.</p>
|
|
<%
|
|
const maxM = Math.max(1, ...byMonth.map(m => m.count));
|
|
const VB = 1000, BH = 320; // viewBox dimensions
|
|
const padL = 28, padR = 10, padB = 26, padT = 10;
|
|
const innerW = VB - padL - padR;
|
|
const innerH = BH - padT - padB;
|
|
const slot = byMonth.length ? innerW / byMonth.length : innerW;
|
|
const bw = Math.min(slot * .62, 40);
|
|
const monthName = (ym) => { const [, mo] = ym.split('-'); return ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'][Number(mo)-1] || ''; };
|
|
const yearOf = (ym) => ym.split('-')[0];
|
|
let prevYear = '';
|
|
%>
|
|
<div class="trend-wrap">
|
|
<svg viewBox="0 0 <%= VB %> <%= BH %>" preserveAspectRatio="none" role="img" aria-label="Bewerbungen pro Monat">
|
|
<% for (let gy = 1; gy <= 4; gy++) { const y = padT + innerH * (1 - gy/4); const val = Math.round(maxM * gy/4); %>
|
|
<line class="trend-grid" x1="<%= padL %>" y1="<%= y %>" x2="<%= VB - padR %>" y2="<%= y %>"></line>
|
|
<text class="trend-axis" x="<%= padL-6 %>" y="<%= y+3 %>" text-anchor="end"><%= val %></text>
|
|
<% } %>
|
|
<% byMonth.forEach((m, i) => { const h = (m.count / maxM) * innerH; const x = padL + slot * i + (slot - bw)/2; const y = padT + innerH - h; const yr = yearOf(m.ym); const showYear = yr !== prevYear; prevYear = yr; %>
|
|
<rect class="trend-bar" x="<%= x %>" y="<%= y %>" width="<%= bw %>" height="<%= Math.max(h, 0.5) %>" rx="3" fill="url(#trendGrad)"></rect>
|
|
<text class="trend-axis" x="<%= x + bw/2 %>" y="<%= BH - 9 %>" text-anchor="middle"><%= monthName(m.ym) %></text>
|
|
<% if (showYear) { %><text class="trend-axis" x="<%= x + bw/2 %>" y="<%= BH - 1 %>" text-anchor="middle" style="font-size:8px;opacity:.7"><%= yr %></text><% } %>
|
|
<title><%= m.ym %>: <%= m.count %> Bewerbung<%= m.count===1?'':'en' %></title>
|
|
<% }); %>
|
|
<defs>
|
|
<linearGradient id="trendGrad" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stop-color="#3b82f6"></stop>
|
|
<stop offset="100%" stop-color="#0ea5e9"></stop>
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
<!-- By Status -->
|
|
<div class="stat-card">
|
|
<h2 class="text-gray-900 dark:text-white">Verteilung nach Status</h2>
|
|
<p class="sub mb-4">Aktueller Status jeder Bewerbung.</p>
|
|
<% if (byStatus.length) {
|
|
const maxS = Math.max(...byStatus.map(s => s.count));
|
|
const statusColor = (s) => ({
|
|
'Entwurf':'#94a3b8','Gesendet':'#3b82f6','Eingangsbestätigung':'#0ea5e9','In Bearbeitung':'#6366f1',
|
|
'Interessiert':'#8b5cf6','Warten auf Rückmeldung':'#a3a3a3','Warten auf meine Antwort':'#a3a3a3',
|
|
'Vorstellungsgespräch':'#10b981','Vertragsverhandlung':'#84cc16','Absage':'#f43f5e','Absage von meiner Seite':'#f97316',
|
|
'Einstellung':'#059669','Keine Rückmeldung':'#cbd5e1'
|
|
}[s] || '#2563eb');
|
|
%>
|
|
<% byStatus.forEach(s => { const pct = Math.round((s.count / maxS) * 100); %>
|
|
<div class="bar-row">
|
|
<span class="bar-label"><span class="swatch" style="background:<%= statusColor(s.status) %>"></span><%= s.status %></span>
|
|
<div class="bar-track"><div class="bar-fill" style="--w:<%= pct %>%;width:<%= pct %>%;background:<%= statusColor(s.status) %>"></div></div>
|
|
<span class="bar-value"><%= s.count %></span>
|
|
</div>
|
|
<% }); %>
|
|
<% } else { %><div class="stat-empty">Keine Daten</div><% } %>
|
|
</div>
|
|
|
|
<!-- By Art -->
|
|
<div class="stat-card">
|
|
<h2 class="text-gray-900 dark:text-white">Verteilung nach Bewerbungsart</h2>
|
|
<p class="sub mb-4">Über welchen Kanal die Bewerbung lief.</p>
|
|
<% if (byArt.length) { const maxA = Math.max(...byArt.map(a => a.count)); const artColor = ['#2563eb','#0ea5e9','#10b981','#8b5cf6','#f59e0b','#f43f5e','#6366f1','#ec4899','#14b8a6']; %>
|
|
<% byArt.forEach((a, i) => { const pct = Math.round((a.count / maxA) * 100); %>
|
|
<div class="bar-row">
|
|
<span class="bar-label"><%= a.art %></span>
|
|
<div class="bar-track"><div class="bar-fill" style="--w:<%= pct %>%;width:<%= pct %>%;background:<%= artColor[i % artColor.length] %>"></div></div>
|
|
<span class="bar-value"><%= a.count %></span>
|
|
</div>
|
|
<% }); %>
|
|
<% } else { %><div class="stat-empty">Keine Daten</div><% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
<!-- Funnel -->
|
|
<div class="stat-card">
|
|
<h2 class="text-gray-900 dark:text-white">Bewerbungs-Funnel</h2>
|
|
<p class="sub mb-5">Wie viele Bewerbungen jede Stufe je erreicht haben.</p>
|
|
<% const topF = Math.max(1, funnel[0].count);
|
|
const fColor = ['#2563eb','#3b82f6','#0ea5e9','#10b981','#059669'];
|
|
funnel.forEach((f, i) => { const pct = Math.round((f.count / topF) * 100); const conv = i === 0 ? '' : ' · ' + Math.round((f.count / Math.max(1, funnel[0].count) * 100)) + '%'; %>
|
|
<div class="funnel-row">
|
|
<div class="funnel-top">
|
|
<span class="funnel-label"><%= f.label %></span>
|
|
<span class="funnel-meta"><%= f.count %><%= conv %></span>
|
|
</div>
|
|
<div class="funnel-track">
|
|
<div class="funnel-fill" style="--w:<%= pct %>%;width:<%= pct %>%;background:<%= fColor[i] %>"><span><%= f.count %></span></div>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
|
|
<!-- By Ort -->
|
|
<div class="stat-card">
|
|
<h2 class="text-gray-900 dark:text-white">Top Standorte</h2>
|
|
<p class="sub mb-4">Wo du dich am häufigsten bewirbst.</p>
|
|
<% if (byOrt.length) { const maxO = Math.max(...byOrt.map(o => o.count)); const ortColor = ['#6366f1','#0ea5e9','#10b981','#f59e0b','#f43f5e','#8b5cf6','#14b8a6','#ec4899','#3b82f6','#a3a3a3']; %>
|
|
<% byOrt.forEach((o, i) => { const pct = Math.round((o.count / maxO) * 100); %>
|
|
<div class="bar-row" style="grid-template-columns:minmax(0,11rem) 1fr 2.5rem">
|
|
<span class="bar-label"><%= o.ort %></span>
|
|
<div class="bar-track"><div class="bar-fill" style="--w:<%= pct %>%;width:<%= pct %>%;background:<%= ortColor[i % ortColor.length] %>"></div></div>
|
|
<span class="bar-value"><%= o.count %></span>
|
|
</div>
|
|
<% }); %>
|
|
<% } else { %><div class="stat-empty">Keine Standorte erfasst</div><% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent activity -->
|
|
<div class="stat-card">
|
|
<h2 class="text-gray-900 dark:text-white">Letzte Aktivität</h2>
|
|
<p class="sub mb-5">Die jüngsten Statusänderungen deiner Bewerbungen.</p>
|
|
<% if (aktivitaet.length) {
|
|
const statusColor = (s) => ({
|
|
'Entwurf':'#94a3b8','Gesendet':'#3b82f6','Eingangsbestätigung':'#0ea5e9','In Bearbeitung':'#6366f1',
|
|
'Interessiert':'#8b5cf6','Warten auf Rückmeldung':'#a3a3a3','Warten auf meine Antwort':'#a3a3a3',
|
|
'Vorstellungsgespräch':'#10b981','Vertragsverhandlung':'#84cc16','Absage':'#f43f5e','Absage von meiner Seite':'#f97316',
|
|
'Einstellung':'#059669','Keine Rückmeldung':'#cbd5e1'
|
|
}[s] || '#2563eb');
|
|
%>
|
|
<div class="tl">
|
|
<% aktivitaet.forEach(a => { %>
|
|
<div class="tl-item" style="--stat-accent:<%= statusColor(a.status) %>">
|
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
<% if (a.bewerbung_id) { %>
|
|
<a href="/bewerbung/<%= a.bewerbung_id %>" class="text-sm font-medium text-gray-900 dark:text-white hover:underline"><%= a.firma || '(ohne Firma)' %></a>
|
|
<span class="text-sm text-gray-500 dark:text-slate-400">· <%= a.stelle || '' %></span>
|
|
<% } else { %>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Gelöschte Bewerbung</span>
|
|
<% } %>
|
|
</div>
|
|
<div class="mt-0.5 flex flex-wrap items-center gap-x-2 text-sm">
|
|
<span class="inline-flex items-center gap-1.5 font-medium" style="color:<%= statusColor(a.status) %>">
|
|
<span class="swatch" style="margin:0"></span><%= a.status %>
|
|
</span>
|
|
<span class="text-gray-400 dark:text-slate-500">· <%= a.datum %></span>
|
|
<% if (a.kommentar) { %><span class="text-gray-500 dark:text-slate-400 truncate">· <%= a.kommentar %></span><% } %>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
<% } else { %><div class="stat-empty">Noch keine Statusänderungen protokolliert.</div><% } %>
|
|
</div>
|
|
|
|
<% } %>
|
|
</main>
|
|
</body>
|
|
</html> |