Files
jobbi-bewerbung/views/bewerbung.ejs
T
thomas c2a629e2c0 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>
2026-06-19 04:01:37 +02:00

219 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>