Refactor UI/views, rework Docker build, untrack local data
- Views umstrukturiert: einstellungen.ejs -> bewerbung.ejs, neues partials/head.ejs, header/footer/index angepasst - CSS umbenannt: style.css -> styles.css - server.js und public/js/main.js ueberarbeitet - Dockerfile auf schlankes Multi-Stage-Setup umgestellt; docker-compose.yml und .dockerignore entfernt - npm-Scripts docker:build/push/deploy ergaenzt - SQLite-DB und .idea aus Git entfernt und via .gitignore ignoriert Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<%- include('partials/head') %>
|
||||
</head>
|
||||
<body class="min-h-screen transition-colors duration-300 bg-gray-50 dark:bg-gray-900" id="body">
|
||||
<%- include('partials/header', { hideSettings: true }) %>
|
||||
|
||||
<%
|
||||
const statusColor = {
|
||||
'Gesendet': 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200',
|
||||
'Eingangsbestätigung': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
||||
'Vorstellungsgespräch': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
|
||||
'Einstellung': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
||||
'Absage': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
||||
'Keine Rückmeldung': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
||||
};
|
||||
const aktuellerStatus = (application.status && application.status.trim()) ? application.status.trim() : 'Ohne Status';
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
%>
|
||||
|
||||
<main class="container mx-auto px-4 py-8 max-w-3xl">
|
||||
<!-- Back link -->
|
||||
<a href="/" class="inline-flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400 hover:underline mb-6">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
Zurück zur Übersicht
|
||||
</a>
|
||||
|
||||
<!-- Title -->
|
||||
<div class="flex flex-wrap items-center gap-3 mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800 dark:text-white">
|
||||
<%= application.firma %> <span class="text-gray-400 dark:text-gray-500">–</span> <%= application.stelle %>
|
||||
</h1>
|
||||
<span class="px-3 py-1 rounded-full text-sm font-medium <%= statusColor[aktuellerStatus] || 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>">
|
||||
<%= aktuellerStatus %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Application data -->
|
||||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">Bewerbungsdaten</h2>
|
||||
<form action="/bewerbung/<%= application.id %>" method="POST" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Datum *</label>
|
||||
<input type="date" name="datum" required value="<%= application.datum %>"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Art der Bewerbung</label>
|
||||
<select name="art" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Bitte wählen --</option>
|
||||
<% artOptions.forEach(option => { %>
|
||||
<option value="<%= option %>" <%= application.art === option ? 'selected' : '' %>><%= option %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Firma *</label>
|
||||
<input type="text" name="firma" required value="<%= application.firma %>"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Stelle *</label>
|
||||
<input type="text" name="stelle" required value="<%= application.stelle %>"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Notizen</label>
|
||||
<textarea name="notizen" rows="6"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Allgemeine Notizen zur Bewerbung..."><%= application.notizen || '' %></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-amber-700 dark:text-amber-400 mb-2 flex items-center gap-1.5">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
</svg>
|
||||
Interne Notizen (nicht im PDF)
|
||||
</label>
|
||||
<textarea name="interne_notizen" rows="4"
|
||||
class="w-full px-3 py-2 border border-amber-300 dark:border-amber-700 rounded-md bg-amber-50 dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Nur für dich – erscheint nicht im Export..."><%= application.interne_notizen || '' %></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||
Bewerbungsdaten speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Status timeline -->
|
||||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white mb-1">Status-Verlauf</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||
Jede Statusänderung wird mit Datum dokumentiert. Der jüngste Eintrag bestimmt den aktuellen Status.
|
||||
</p>
|
||||
|
||||
<!-- Existing entries -->
|
||||
<% if (verlauf.length > 0) { %>
|
||||
<ol class="relative border-l border-gray-200 dark:border-gray-600 ml-2 space-y-6 mb-8">
|
||||
<% verlauf.forEach(eintrag => { %>
|
||||
<li class="ml-6">
|
||||
<span class="absolute -left-1.5 mt-2 w-3 h-3 rounded-full bg-blue-500 ring-4 ring-white dark:ring-gray-800"></span>
|
||||
<form action="/bewerbung/<%= application.id %>/verlauf/<%= eintrag.id %>" method="POST"
|
||||
class="bg-gray-50 dark:bg-gray-700/40 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Datum</label>
|
||||
<input type="date" name="datum" required value="<%= eintrag.datum %>"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Status</label>
|
||||
<select name="status" required
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<% statusOptions.forEach(option => { %>
|
||||
<option value="<%= option %>" <%= eintrag.status === option ? 'selected' : '' %>><%= option %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Kommentar</label>
|
||||
<textarea name="kommentar" rows="2"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Was ist passiert?"><%= eintrag.kommentar || '' %></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="submit"
|
||||
class="px-3 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form action="/bewerbung/<%= application.id %>/verlauf/<%= eintrag.id %>/delete" method="POST" class="mt-1 text-right"
|
||||
onsubmit="return confirm('Diesen Verlaufseintrag löschen?');">
|
||||
<button type="submit"
|
||||
class="text-xs text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 hover:underline">
|
||||
Eintrag löschen
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ol>
|
||||
<% } else { %>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-8">Noch keine Statusänderungen dokumentiert.</p>
|
||||
<% } %>
|
||||
|
||||
<!-- Add new entry -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Statusänderung hinzufügen</h3>
|
||||
<form action="/bewerbung/<%= application.id %>/verlauf" method="POST" class="space-y-3">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Datum</label>
|
||||
<input type="date" name="datum" required value="<%= today %>"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Status</label>
|
||||
<select name="status" required
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<% statusOptions.forEach(option => { %>
|
||||
<option value="<%= option %>"><%= option %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Kommentar</label>
|
||||
<textarea name="kommentar" rows="2"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Was ist passiert?"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||||
Statusänderung hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<%- include('partials/footer') %>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const body = document.getElementById('body');
|
||||
const dm = localStorage.getItem('darkMode');
|
||||
if (dm === 'enabled' || (!dm && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
body.classList.add('dark');
|
||||
}
|
||||
const toggle = document.getElementById('darkModeToggle');
|
||||
const sun = document.getElementById('sunIcon');
|
||||
const moon = document.getElementById('moonIcon');
|
||||
function sync() {
|
||||
const d = body.classList.contains('dark');
|
||||
if (sun) sun.classList.toggle('hidden', d);
|
||||
if (moon) moon.classList.toggle('hidden', !d);
|
||||
}
|
||||
sync();
|
||||
if (toggle) toggle.addEventListener('click', () => {
|
||||
body.classList.toggle('dark');
|
||||
localStorage.setItem('darkMode', body.classList.contains('dark') ? 'enabled' : 'disabled');
|
||||
sync();
|
||||
});
|
||||
const cy = document.getElementById('currentYear');
|
||||
if (cy) cy.textContent = new Date().getFullYear();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,62 +0,0 @@
|
||||
<%- include('partials/header') %>
|
||||
|
||||
<div style="max-width:42rem;">
|
||||
|
||||
<div class="page-header" style="margin-bottom:1.5rem;">
|
||||
<div>
|
||||
<h1 class="page-title">Einstellungen</h1>
|
||||
<p class="page-meta">Diese Daten erscheinen automatisch in jedem PDF-Export.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (gespeichert) { %>
|
||||
<div class="alert alert-success">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="flex-shrink:0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
Einstellungen wurden gespeichert.
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="settings-card">
|
||||
<form method="POST" action="/einstellungen" class="settings-form">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="name">Vollständiger Name</label>
|
||||
<input type="text" id="name" name="name" maxlength="500" class="form-input"
|
||||
value="<%= settings.name || '' %>"
|
||||
placeholder="Vorname Nachname">
|
||||
<p class="form-hint">Erscheint als Absender und in der Unterschriftszeile des PDFs.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="adresse">Adresse</label>
|
||||
<textarea id="adresse" name="adresse" rows="3" maxlength="1000" class="form-textarea"
|
||||
placeholder="Musterstraße 1 12345 Musterstadt"><%= settings.adresse || '' %></textarea>
|
||||
<p class="form-hint">Mehrzeilig – erscheint im Briefkopf des PDFs.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="kundennummer">Jobcenter-Kundennummer</label>
|
||||
<input type="text" id="kundennummer" name="kundennummer" maxlength="100" class="form-input"
|
||||
value="<%= settings.kundennummer || '' %>"
|
||||
placeholder="z.B. BG-12345678">
|
||||
<p class="form-hint">Ihre Kundennummer beim Jobcenter / der Agentur für Arbeit.</p>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;justify-content:flex-end;padding-top:.5rem;">
|
||||
<button type="submit" class="btn btn-primary">Einstellungen speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="info-box" style="margin-top:1rem;">
|
||||
<strong>PDF-Hinweis</strong>
|
||||
Das exportierte PDF enthält Ihren Briefkopf mit den obigen Daten, einen Zusammenfassungssatz,
|
||||
eine vollständige Bewerbungstabelle sowie eine Bestätigungs- und Unterschriftszeile.
|
||||
Starten Sie den Export über „PDF exportieren" auf der Übersichtsseite.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<%- include('partials/footer') %>
|
||||
+595
-284
@@ -1,298 +1,609 @@
|
||||
<%- include('partials/header') %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<%- include('partials/head') %>
|
||||
</head>
|
||||
<body class="min-h-screen transition-colors duration-300" id="body">
|
||||
<%- include('partials/header') %>
|
||||
|
||||
<%# ── Embed server data for client-side use ──────────────────────────────── %>
|
||||
<script type="application/json" id="bewerbungenData"><%- bewerbungenJson %></script>
|
||||
<script>
|
||||
const CURRENT_MONAT = <%= monat %>;
|
||||
const CURRENT_JAHR = <%= jahr %>;
|
||||
const MONATE_DE = <%- JSON.stringify(monate) %>;
|
||||
</script>
|
||||
|
||||
<%# ── Page header ────────────────────────────────────────────────────────── %>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Bewerbungsübersicht</h1>
|
||||
<div class="page-meta">
|
||||
<span><%= monatName %> <%= jahr %></span>
|
||||
<% if (settings && settings.kundennummer) { %>
|
||||
<span class="sep">·</span>
|
||||
<span class="tag">KdNr. <%= settings.kundennummer %></span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Filter + PDF %>
|
||||
<form method="GET" action="/" class="filter-bar">
|
||||
<select name="monat" class="form-select btn-sm" style="width:auto;">
|
||||
<% monate.forEach((m, i) => { %>
|
||||
<option value="<%= i + 1 %>" <%= (i + 1 === monat) ? 'selected' : '' %>><%= m %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
<select name="jahr" class="form-select btn-sm" style="width:auto;">
|
||||
<% jahre.forEach(j => { %>
|
||||
<option value="<%= j %>" <%= (j === jahr) ? 'selected' : '' %>><%= j %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-secondary btn-sm">Filtern</button>
|
||||
|
||||
<button type="button" class="btn btn-primary btn-sm" onclick="generatePDF(<%= monat %>, <%= jahr %>)">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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"/>
|
||||
</svg>
|
||||
PDF exportieren
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<%# ── Error banner ───────────────────────────────────────────────────────── %>
|
||||
<% if (fehler === 'pflichtfelder') { %>
|
||||
<div class="alert alert-error">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="flex-shrink:0">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
Bitte alle Pflichtfelder ausfüllen: Datum, Firma und Stelle sind erforderlich.
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<%# ── Stats grid ─────────────────────────────────────────────────────────── %>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card s-total">
|
||||
<div class="stat-label">Gesamt</div>
|
||||
<div class="stat-value" data-count="<%= stats.gesamt %>"><%= stats.gesamt %></div>
|
||||
</div>
|
||||
<div class="stat-card s-positiv">
|
||||
<div class="stat-label">Positiv</div>
|
||||
<div class="stat-value" data-count="<%= stats.positiv %>"><%= stats.positiv %></div>
|
||||
</div>
|
||||
<div class="stat-card s-absage">
|
||||
<div class="stat-label">Absagen</div>
|
||||
<div class="stat-value" data-count="<%= stats.absagen %>"><%= stats.absagen %></div>
|
||||
</div>
|
||||
<div class="stat-card s-pending">
|
||||
<div class="stat-label">Ausstehend</div>
|
||||
<div class="stat-value" data-count="<%= stats.ausstehend %>"><%= stats.ausstehend %></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# ── Action bar ─────────────────────────────────────────────────────────── %>
|
||||
<div class="action-bar">
|
||||
<span class="entry-count"><%= bewerbungen.length %> Eintr<%= bewerbungen.length === 1 ? 'ag' : 'äge' %></span>
|
||||
<button class="btn btn-primary btn-sm" onclick="openAddModal()">
|
||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
Neue Bewerbung
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<%# ── Data table ─────────────────────────────────────────────────────────── %>
|
||||
<div class="data-card">
|
||||
<% if (bewerbungen.length === 0) { %>
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<svg width="22" height="22" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
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"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="empty-title">Keine Bewerbungen für <%= monatName %> <%= jahr %></p>
|
||||
<p class="empty-sub">Klicken Sie auf „Neue Bewerbung", um Ihren ersten Eintrag hinzuzufügen.</p>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datum</th>
|
||||
<th>Firma</th>
|
||||
<th>Stelle</th>
|
||||
<th>Art</th>
|
||||
<th>Status</th>
|
||||
<th class="hidden md:table-cell">Notizen</th>
|
||||
<th style="text-align:right; padding-right:1.25rem;">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% bewerbungen.forEach(b => { %>
|
||||
<tr>
|
||||
<td class="td-date">
|
||||
<%= b.datum ? b.datum.split('-').reverse().join('.') : '' %>
|
||||
</td>
|
||||
<td class="td-firma"><%= b.firma %></td>
|
||||
<td class="td-stelle"><%= b.stelle %></td>
|
||||
<td>
|
||||
<% if (b.art) { %>
|
||||
<span class="td-art"><%= b.art %></span>
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<% if (b.status) { %>
|
||||
<span class="status-chip <%= statusClass(b.status) %>"><%= b.status %></span>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class="td-notizen hidden md:table-cell"><%= b.notizen || '' %></td>
|
||||
<td>
|
||||
<div class="row-actions flex items-center justify-end gap-1" style="padding-right:0.25rem;">
|
||||
<button class="row-btn btn-edit" title="Bearbeiten"
|
||||
onclick="openEditModal(<%= b.id %>)">
|
||||
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="row-btn btn-del" title="Löschen"
|
||||
onclick="openDeleteModal(<%= b.id %>, '<%= b.firma.replace(/\\/g, '\\\\').replace(/'/g, "\\'") %>', '<%= b.stelle.replace(/\\/g, '\\\\').replace(/'/g, "\\'") %>')">
|
||||
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
<!-- Filter Section -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">Filter</h2>
|
||||
<form id="filterForm" class="flex flex-wrap gap-4 items-end">
|
||||
<div class="flex-1 min-w-32">
|
||||
<label for="filterMonth" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Monat
|
||||
</label>
|
||||
<select
|
||||
id="filterMonth"
|
||||
name="month"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Alle Monate --</option>
|
||||
<% availableMonths.forEach(m => { %>
|
||||
<option value="<%= m.month %>"><%= m.month %> - <%= m.year %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% }) %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<%# ══════════════════════════════════════════════════════════════════════════ %>
|
||||
<%# Modal: Neue / Bewerbung bearbeiten %>
|
||||
<%# ══════════════════════════════════════════════════════════════════════════ %>
|
||||
<div id="bewerbungModal" class="modal-backdrop" onclick="closeOnBackdrop(event,'bewerbungModal')">
|
||||
<div class="modal-box" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
||||
|
||||
<div class="modal-head">
|
||||
<h2 id="modalTitle" class="modal-title">Neue Bewerbung</h2>
|
||||
<button class="modal-close" onclick="closeModal('bewerbungModal')" aria-label="Schließen">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="bewerbungForm" method="POST" action="/bewerbungen">
|
||||
<input type="hidden" name="monat" value="<%= monat %>">
|
||||
<input type="hidden" name="jahr" value="<%= jahr %>">
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="datum">Datum <span class="req">*</span></label>
|
||||
<input type="date" id="datum" name="datum" required class="form-input">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="art">Art der Bewerbung</label>
|
||||
<select id="art" name="art" class="form-select">
|
||||
<option value="">— bitte wählen —</option>
|
||||
<% artOptionen.forEach(a => { %>
|
||||
<option value="<%= a %>"><%= a %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group span-2">
|
||||
<label class="form-label" for="firma">Firma / Unternehmen <span class="req">*</span></label>
|
||||
<input type="text" id="firma" name="firma" required maxlength="500" class="form-input"
|
||||
placeholder="z.B. Muster GmbH">
|
||||
</div>
|
||||
|
||||
<div class="form-group span-2">
|
||||
<label class="form-label" for="stelle">Stelle / Position <span class="req">*</span></label>
|
||||
<input type="text" id="stelle" name="stelle" required maxlength="500" class="form-input"
|
||||
placeholder="z.B. Fachinformatiker / Softwareentwickler">
|
||||
</div>
|
||||
|
||||
<div class="form-group span-2">
|
||||
<label class="form-label" for="status">Status</label>
|
||||
<select id="status" name="status" class="form-select">
|
||||
<option value="">— bitte wählen —</option>
|
||||
<% statusOptionen.forEach(s => { %>
|
||||
<option value="<%= s %>"><%= s %></option>
|
||||
<% }) %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group span-2">
|
||||
<label class="form-label" for="notizen">Notizen</label>
|
||||
<textarea id="notizen" name="notizen" rows="3" maxlength="2000" class="form-textarea"
|
||||
placeholder="Ansprechpartner, Referenznummer, Gehaltsvorstellung …"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-32">
|
||||
<label for="filterYear" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Jahr
|
||||
</label>
|
||||
<select
|
||||
id="filterYear"
|
||||
name="year"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Alle Jahre --</option>
|
||||
<% const years = [...new Set(availableMonths.map(m => m.year))].sort().reverse(); %>
|
||||
<% years.forEach(y => { %>
|
||||
<option value="<%= y %>"><%= y %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||
Filtern
|
||||
</button>
|
||||
<a
|
||||
href="/"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
Zurücksetzen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
id="exportPdfBtn"
|
||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors flex items-center space-x-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>PDF Export</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
id="addApplicationBtn"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors flex items-center space-x-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
<span>Bewerbung hinzufügen</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-foot">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal('bewerbungModal')">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Statistics Section -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white mb-6">Statistik</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- Total Applications -->
|
||||
<div class="bg-blue-50 dark:bg-gray-700 rounded-lg p-4 text-center">
|
||||
<div class="text-3xl font-bold text-blue-600 dark:text-blue-400"><%= statistics.total %></div>
|
||||
<div class="text-gray-600 dark:text-gray-400 mt-2">Gesamtbewerbungen</div>
|
||||
</div>
|
||||
|
||||
<!-- By Application Type -->
|
||||
<div class="bg-green-50 dark:bg-gray-700 rounded-lg p-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Nach Bewerbungsart</h3>
|
||||
<div class="space-y-2">
|
||||
<% if (statistics.byArt.length > 0) { %>
|
||||
<% statistics.byArt.forEach(item => { %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400"><%= item.art %></span>
|
||||
<span class="font-medium text-gray-800 dark:text-white"><%= item.count %></span>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Keine Daten verfügbar</p>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- By Status -->
|
||||
<div class="bg-purple-50 dark:bg-gray-700 rounded-lg p-4">
|
||||
<h3 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Nach Status</h3>
|
||||
<div class="space-y-2">
|
||||
<% if (statistics.byStatus.length > 0) { %>
|
||||
<% statistics.byStatus.forEach(item => { %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400"><%= item.status %></span>
|
||||
<span class="font-medium text-gray-800 dark:text-white"><%= item.count %></span>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Keine Daten verfügbar</p>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Applications Table -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 dark:bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Datum</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Firma</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Stelle</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Art</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<% if (applications.length > 0) { %>
|
||||
<%
|
||||
// Status-Reihenfolge synchron zu public/js/main.js halten
|
||||
const statusOrder = ['Gesendet', 'Eingangsbestätigung', 'Vorstellungsgespräch',
|
||||
'Einstellung', 'Absage', 'Keine Rückmeldung'];
|
||||
const statusDot = {
|
||||
'Gesendet': 'bg-indigo-500',
|
||||
'Eingangsbestätigung': 'bg-blue-500',
|
||||
'Vorstellungsgespräch': 'bg-yellow-500',
|
||||
'Einstellung': 'bg-green-500',
|
||||
'Absage': 'bg-red-500',
|
||||
'Keine Rückmeldung': 'bg-gray-400',
|
||||
'Ohne Status': 'bg-gray-300'
|
||||
};
|
||||
const groups = {};
|
||||
applications.forEach(a => {
|
||||
const key = (a.status && a.status.trim()) ? a.status.trim() : 'Ohne Status';
|
||||
(groups[key] = groups[key] || []).push(a);
|
||||
});
|
||||
const orderedKeys = [
|
||||
...statusOrder.filter(s => groups[s]),
|
||||
...Object.keys(groups).filter(k => !statusOrder.includes(k))
|
||||
];
|
||||
%>
|
||||
<% orderedKeys.forEach(statusKey => { %>
|
||||
<tbody class="bg-gray-100 dark:bg-gray-900/40 border-b border-gray-200 dark:border-gray-700">
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="inline-block w-2.5 h-2.5 rounded-full <%= statusDot[statusKey] || 'bg-gray-300' %>"></span>
|
||||
<span class="text-sm font-semibold uppercase tracking-wider text-gray-700 dark:text-gray-200"><%= statusKey %></span>
|
||||
<span class="text-xs font-medium px-2 py-0.5 rounded-full bg-white dark:bg-gray-700 text-gray-600 dark:text-gray-300"><%= groups[statusKey].length %></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% groups[statusKey].forEach(app => { %>
|
||||
<%
|
||||
const hatNotizen = app.notizen && app.notizen.trim().length > 0;
|
||||
const hatInterne = app.interne_notizen && app.interne_notizen.trim().length > 0;
|
||||
const hatVerlauf = app.verlauf && app.verlauf.length > 0;
|
||||
const hatDetails = hatNotizen || hatInterne || hatVerlauf;
|
||||
const padB = hatDetails ? 'pb-2' : 'pb-4';
|
||||
%>
|
||||
<tbody class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<td class="px-6 pt-4 <%= padB %> whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
||||
<%= new Date(app.datum).toLocaleDateString('de-DE') %>
|
||||
</td>
|
||||
<td class="px-6 pt-4 <%= padB %> whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white">
|
||||
<%= app.firma %>
|
||||
</td>
|
||||
<td class="px-6 pt-4 <%= padB %> whitespace-nowrap text-sm text-gray-900 dark:text-white">
|
||||
<%= app.stelle %>
|
||||
</td>
|
||||
<td class="px-6 pt-4 <%= padB %> whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
|
||||
<%= app.art || '-' %>
|
||||
</td>
|
||||
<td class="px-6 pt-4 <%= padB %> whitespace-nowrap text-sm">
|
||||
<span class="px-2 py-1 rounded-full text-xs font-medium
|
||||
<%= app.status === 'Einstellung' ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' :
|
||||
app.status === 'Absage' ? 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' :
|
||||
app.status === 'Vorstellungsgespräch' ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-100' :
|
||||
'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300' %>">
|
||||
<%= app.status || '-' %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 pt-4 <%= padB %> whitespace-nowrap text-sm align-top">
|
||||
<a href="/bewerbung/<%= app.id %>"
|
||||
class="inline-block align-middle text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 mr-3"
|
||||
aria-label="Bearbeiten">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<button
|
||||
class="delete-btn align-middle text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
||||
data-id="<%= app.id %>">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<% if (hatDetails) { %>
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<td colspan="6" class="px-6 pb-5 pt-0 space-y-3">
|
||||
<% if (hatVerlauf) { %>
|
||||
<div class="rounded-lg border-l-4 border-indigo-400 dark:border-indigo-500 bg-indigo-50/60 dark:bg-gray-700/40 px-4 py-3">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<svg class="w-4 h-4 text-indigo-500 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span class="text-xs font-semibold uppercase tracking-wider text-indigo-600 dark:text-indigo-400">Status-Verlauf</span>
|
||||
</div>
|
||||
<ol class="space-y-1.5">
|
||||
<% app.verlauf.forEach(v => { %>
|
||||
<li class="flex flex-wrap gap-x-2 text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400 whitespace-nowrap"><%= new Date(v.datum).toLocaleDateString('de-DE') %></span>
|
||||
<span class="font-semibold text-gray-800 dark:text-gray-100"><%= v.status %></span>
|
||||
<% if (v.kommentar && v.kommentar.trim()) { %>
|
||||
<span class="text-gray-600 dark:text-gray-300 whitespace-pre-wrap break-words">– <%= v.kommentar %></span>
|
||||
<% } %>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ol>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (hatNotizen) { %>
|
||||
<div class="rounded-lg border-l-4 border-blue-400 dark:border-blue-500 bg-blue-50/70 dark:bg-gray-700/40 px-4 py-3">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<svg class="w-4 h-4 text-blue-500 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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 class="text-xs font-semibold uppercase tracking-wider text-blue-600 dark:text-blue-400">Notizen</span>
|
||||
</div>
|
||||
<p class="whitespace-pre-wrap break-words text-base leading-relaxed text-gray-800 dark:text-gray-200"><%= app.notizen %></p>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (hatInterne) { %>
|
||||
<div class="rounded-lg border-l-4 border-amber-400 dark:border-amber-500 bg-amber-50/70 dark:bg-gray-700/40 px-4 py-3">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<svg class="w-4 h-4 text-amber-500 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
</svg>
|
||||
<span class="text-xs font-semibold uppercase tracking-wider text-amber-600 dark:text-amber-400">Interne Notizen · nicht im PDF</span>
|
||||
</div>
|
||||
<p class="whitespace-pre-wrap break-words text-base leading-relaxed text-gray-800 dark:text-gray-200"><%= app.interne_notizen %></p>
|
||||
</div>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<tbody class="bg-white dark:bg-gray-800">
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
|
||||
<p>Keine Bewerbungen gefunden.</p>
|
||||
<p class="mt-2 text-sm">Klicken Sie auf "Bewerbung hinzufügen", um Ihre erste Bewerbung einzutragen.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% } %>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<%# ══════════════════════════════════════════════════════════════════════════ %>
|
||||
<%# Modal: Löschen bestätigen %>
|
||||
<%# ══════════════════════════════════════════════════════════════════════════ %>
|
||||
<div id="deleteModal" class="modal-backdrop" onclick="closeOnBackdrop(event,'deleteModal')">
|
||||
<div class="modal-box compact" role="dialog" aria-modal="true">
|
||||
<%- include('partials/footer') %>
|
||||
|
||||
<div class="modal-head">
|
||||
<h2 class="modal-title">Eintrag löschen</h2>
|
||||
<button class="modal-close" onclick="closeModal('deleteModal')" aria-label="Schließen">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Settings Modal -->
|
||||
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 dark:text-white">Benutzereinstellungen</h2>
|
||||
<button id="closeSettingsModal" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="settingsForm" class="space-y-4">
|
||||
<div>
|
||||
<label for="userName" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Vollständiger Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="userName"
|
||||
name="name"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||||
placeholder="Max Mustermann"
|
||||
value="<%= settings.name || '' %>">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="userAddress" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Adresse *
|
||||
</label>
|
||||
<textarea
|
||||
id="userAddress"
|
||||
name="adresse"
|
||||
required
|
||||
rows="2"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||||
placeholder="Musterstraße 1, 12345 Musterstadt"><%= settings.adresse || '' %></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="customerNumber" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Jobcenter Kundennummer
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="customerNumber"
|
||||
name="kundennummer"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||||
placeholder="z.B. 123456789"
|
||||
value="<%= settings.kundennummer || '' %>">
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
id="cancelSettings"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="flex gap-3">
|
||||
<div class="flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full"
|
||||
style="background:var(--red-dim);">
|
||||
<svg width="18" height="18" fill="none" stroke="var(--red)" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
<!-- Add/Edit Application Modal -->
|
||||
<div id="applicationModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 id="modalTitle" class="text-xl font-bold text-gray-800 dark:text-white">Bewerbung hinzufügen</h2>
|
||||
<button id="closeApplicationModal" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="applicationForm" class="space-y-4">
|
||||
<input type="hidden" id="applicationId" name="id">
|
||||
|
||||
<div>
|
||||
<label for="applicationDatum" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Datum *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
id="applicationDatum"
|
||||
name="datum"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationFirma" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Firma *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="applicationFirma"
|
||||
name="firma"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||||
placeholder="Firmenname">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationStelle" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Stelle *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="applicationStelle"
|
||||
name="stelle"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||||
placeholder="Stellenbezeichnung">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationArt" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Art der Bewerbung
|
||||
</label>
|
||||
<select
|
||||
id="applicationArt"
|
||||
name="art"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Bitte wählen --</option>
|
||||
<% artOptions.forEach(option => { %>
|
||||
<option value="<%= option %>"><%= option %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationStatus" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Status (Anfangsstatus)
|
||||
</label>
|
||||
<select
|
||||
id="applicationStatus"
|
||||
name="status"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Bitte wählen --</option>
|
||||
<% statusOptions.forEach(option => { %>
|
||||
<option value="<%= option %>"><%= option %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationKommentar" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Kommentar zur Statusänderung
|
||||
</label>
|
||||
<textarea
|
||||
id="applicationKommentar"
|
||||
name="kommentar"
|
||||
rows="2"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Optional – z.B. „per E-Mail an Frau Müller“"></textarea>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Weitere Statusänderungen dokumentierst du später auf der Bearbeiten-Seite.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationNotizen" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Notizen
|
||||
</label>
|
||||
<textarea
|
||||
id="applicationNotizen"
|
||||
name="notizen"
|
||||
rows="4"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Allgemeine Notizen zur Bewerbung..."></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="applicationInterneNotizen" class="block text-sm font-medium text-amber-700 dark:text-amber-400 mb-2 flex items-center gap-1.5">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"></path>
|
||||
</svg>
|
||||
Interne Notizen (nicht im PDF)
|
||||
</label>
|
||||
<textarea
|
||||
id="applicationInterneNotizen"
|
||||
name="interne_notizen"
|
||||
rows="3"
|
||||
class="w-full px-3 py-2 border border-amber-300 dark:border-amber-700 rounded-md bg-amber-50 dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed"
|
||||
placeholder="Nur für dich – erscheint nicht im Export..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
id="cancelApplication"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p style="font-size:.9375rem;font-weight:600;color:var(--text);margin-bottom:.375rem;">
|
||||
Sicher löschen?
|
||||
</p>
|
||||
<p id="deleteInfo" style="font-size:.875rem;color:var(--text-2);margin-bottom:.5rem;"></p>
|
||||
<p style="font-size:.75rem;color:var(--text-muted);">
|
||||
Diese Aktion kann nicht rückgängig gemacht werden.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="deleteForm" method="POST">
|
||||
<input type="hidden" name="monat" value="<%= monat %>">
|
||||
<input type="hidden" name="jahr" value="<%= jahr %>">
|
||||
<div class="modal-foot">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal('deleteModal')">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-danger">Endgültig löschen</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div id="deleteModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 dark:text-white">Bewerbung löschen</h2>
|
||||
<button id="closeDeleteModal" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-6">
|
||||
Sind Sie sicher, dass Sie diese Bewerbung löschen möchten? Dieser Vorgang kann nicht rückgängig gemacht werden.
|
||||
</p>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
id="cancelDelete"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="confirmDelete"
|
||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors">
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- PDF Export Modal -->
|
||||
<div id="pdfExportModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
||||
<div class="flex items-center justify-center min-h-screen p-4">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 dark:text-white">PDF Export</h2>
|
||||
<button id="closePdfModal" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form id="pdfExportForm" class="space-y-4">
|
||||
<div>
|
||||
<label for="pdfMonth" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Monat
|
||||
</label>
|
||||
<select
|
||||
id="pdfMonth"
|
||||
name="month"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Alle Monate --</option>
|
||||
<% availableMonths.forEach(m => { %>
|
||||
<option value="<%= m.month %>"><%= m.month %> - <%= m.year %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="pdfYear" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Jahr
|
||||
</label>
|
||||
<select
|
||||
id="pdfYear"
|
||||
name="year"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||||
<option value="">-- Alle Jahre --</option>
|
||||
<% const pdfYears = [...new Set(availableMonths.map(m => m.year))].sort().reverse(); %>
|
||||
<% pdfYears.forEach(y => { %>
|
||||
<option value="<%= y %>"><%= y %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
id="cancelPdfExport"
|
||||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||||
PDF generieren
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Server-side helper: map status string → CSS class ─────────────────────── %>
|
||||
<% function statusClass(s) {
|
||||
const map = {
|
||||
'Gesendet': 'st-gesendet',
|
||||
'Eingangsbestätigung': 'st-eingang',
|
||||
'Vorstellungsgespräch': 'st-vorstellung',
|
||||
'Absage': 'st-absage',
|
||||
'Einstellung': 'st-einstellung',
|
||||
'Keine Rückmeldung': 'st-keine'
|
||||
};
|
||||
return map[s] || 'st-keine';
|
||||
} %>
|
||||
|
||||
<%- include('partials/footer') %>
|
||||
<!-- Load main JavaScript -->
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
</div><%# /page-wrapper %>
|
||||
|
||||
<footer class="app-footer">
|
||||
Bewerbungs-Tracker — Lokale Bewerbungsverwaltung
|
||||
</footer>
|
||||
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<footer class="bg-gray-100 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-12 py-6">
|
||||
<div class="container mx-auto px-4 text-center">
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||
Bewerbungs-Tracker für Jobcenter Grundsicherung |
|
||||
<span id="currentYear"></span>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bewerbungs-Tracker</title>
|
||||
<!-- Tailwind CSS CDN -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
+41
-48
@@ -1,49 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bewerbungs-Tracker</title>
|
||||
|
||||
<%# Synchronously apply saved dark mode preference before paint to prevent flash %>
|
||||
<script>
|
||||
(function () {
|
||||
if (localStorage.getItem('darkMode') !== 'false') {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<%# Tailwind CDN — used for layout utilities (flex, grid, overflow, responsive) %>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
corePlugins: { preflight: false }
|
||||
};
|
||||
</script>
|
||||
|
||||
<%# jsPDF + autoTable for client-side PDF generation %>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="app-nav">
|
||||
<a href="/" class="nav-brand">
|
||||
<span class="brand-dot"></span>
|
||||
Bewerbungs-Tracker
|
||||
</a>
|
||||
|
||||
<div class="nav-links">
|
||||
<a href="/" class="nav-link <%= (typeof currentPage !== 'undefined' && currentPage === 'uebersicht') ? 'active' : '' %>">Übersicht</a>
|
||||
<a href="/einstellungen" class="nav-link <%= (typeof currentPage !== 'undefined' && currentPage === 'einstellungen') ? 'active' : '' %>">Einstellungen</a>
|
||||
<header class="bg-gradient-to-r from-blue-700 to-blue-900 dark:from-gray-800 dark:to-gray-900 shadow-lg">
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-3">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
|
||||
<h1 class="text-xl font-bold text-white">Bewerbungs-Tracker</h1>
|
||||
</a>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- Dark mode toggle -->
|
||||
<button
|
||||
id="darkModeToggle"
|
||||
class="p-2 rounded-full bg-white/20 hover:bg-white/30 transition-colors text-white"
|
||||
aria-label="Dark Mode umschalten"
|
||||
>
|
||||
<svg id="sunIcon" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||
</svg>
|
||||
<svg id="moonIcon" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Settings button -->
|
||||
<% if (typeof hideSettings === 'undefined' || !hideSettings) { %>
|
||||
<button
|
||||
id="settingsBtn"
|
||||
class="p-2 rounded-full bg-white/20 hover:bg-white/30 transition-colors text-white"
|
||||
aria-label="Einstellungen"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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.065 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.065c-.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.065-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" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="darkModeToggle" class="dark-toggle" title="Darkmodus umschalten" aria-label="Darkmodus umschalten"></button>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrapper">
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user