The overview table used auto layout with whitespace-nowrap on Firma/Stelle, so a long name expanded the columns beyond w-full and the overflow-hidden container clipped the right edge — hiding Bearbeiten/Löschen. Switch to table-fixed with a colgroup for stable column widths, allow Firma/Stelle to wrap (break-words, align-top), and make the wrapper overflow-x-auto so the table scrolls horizontally on narrow viewports instead of clipping. Co-Authored-By: Claude <noreply@anthropic.com>
692 lines
43 KiB
Plaintext
692 lines
43 KiB
Plaintext
<!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 text-gray-900 dark:text-gray-100" id="body">
|
||
<%- include('partials/header') %>
|
||
|
||
<main class="container mx-auto px-4 py-8">
|
||
<!-- Search -->
|
||
<div class="flex justify-center mb-8">
|
||
<div class="relative w-full max-w-xl">
|
||
<svg class="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 dark:text-gray-500 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||
</svg>
|
||
<input id="firmaSearch" type="text" placeholder="Firma suchen …" autocomplete="off"
|
||
class="w-full pl-12 pr-10 py-3 rounded-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-800 dark:text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition">
|
||
<button id="firmaSearchClear" type="button" aria-label="Suche zurücksetzen"
|
||
class="hidden absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-full text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||
<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="M6 18L18 6M6 6l12 12"></path></svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 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>
|
||
|
||
<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
|
||
type="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
|
||
type="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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Applications Table -->
|
||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-x-auto">
|
||
<table class="w-full min-w-[720px] table-fixed">
|
||
<colgroup>
|
||
<col class="w-24">
|
||
<col class="w-[22%]">
|
||
<col class="w-[28%]">
|
||
<col class="w-28">
|
||
<col class="w-36">
|
||
<col class="w-24">
|
||
</colgroup>
|
||
<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 = ['Entwurf', 'Gesendet', 'Eingangsbestätigung', 'Vorstellungsgespräch',
|
||
'Einstellung', 'Absage', 'Keine Rückmeldung'];
|
||
const statusDot = {
|
||
'Entwurf': 'bg-purple-500',
|
||
'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="status-group 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 data-group-count 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="app-row bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700" data-search="<%= ((app.firma || '') + ' ' + (app.stelle || '')).toLowerCase().replace(/"/g, '') %>">
|
||
<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 %> align-top text-sm font-medium text-gray-900 dark:text-white break-words">
|
||
<%= app.firma %>
|
||
</td>
|
||
<td class="px-6 pt-4 <%= padB %> align-top text-sm text-gray-900 dark:text-white break-words">
|
||
<%= 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' :
|
||
app.status === 'Entwurf' ? 'bg-purple-100 text-purple-800 dark:bg-purple-800 dark:text-purple-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>
|
||
<% }); %>
|
||
<% }); %>
|
||
<tbody id="searchNoResults" class="bg-white dark:bg-gray-800" style="display:none">
|
||
<tr>
|
||
<td colspan="6" class="px-6 py-12 text-center text-gray-500 dark:text-gray-400">
|
||
<p>Keine Firma gefunden für „<span id="searchNoResultsTerm"></span>".</p>
|
||
</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>
|
||
|
||
<script>
|
||
// Live company search — filters the applications table by firma/stelle.
|
||
(function () {
|
||
var input = document.getElementById('firmaSearch');
|
||
if (!input) return;
|
||
var clear = document.getElementById('firmaSearchClear');
|
||
var appRows = Array.prototype.slice.call(document.querySelectorAll('tbody.app-row'));
|
||
var groupHeaders = Array.prototype.slice.call(document.querySelectorAll('tbody.status-group'));
|
||
var noResults = document.getElementById('searchNoResults');
|
||
var noResultsTerm = document.getElementById('searchNoResultsTerm');
|
||
|
||
function filter() {
|
||
var q = input.value.trim().toLowerCase();
|
||
if (clear) clear.classList.toggle('hidden', !q);
|
||
|
||
appRows.forEach(function (tb) {
|
||
var match = !q || (tb.dataset.search || '').indexOf(q) !== -1;
|
||
tb.style.display = match ? '' : 'none';
|
||
});
|
||
|
||
// Refresh each status group: hide headers with no visible rows, update counts.
|
||
groupHeaders.forEach(function (gh) {
|
||
var visible = 0, node = gh.nextElementSibling;
|
||
while (node && !node.classList.contains('status-group')) {
|
||
if (node.classList.contains('app-row') && node.style.display !== 'none') visible++;
|
||
node = node.nextElementSibling;
|
||
}
|
||
gh.style.display = (visible === 0 && q) ? 'none' : '';
|
||
var countEl = gh.querySelector('[data-group-count]');
|
||
if (countEl) countEl.textContent = visible;
|
||
});
|
||
|
||
var anyVisible = appRows.some(function (tb) { return tb.style.display !== 'none'; });
|
||
if (noResults) {
|
||
noResults.style.display = (!anyVisible && q) ? '' : 'none';
|
||
if (noResultsTerm) noResultsTerm.textContent = input.value.trim();
|
||
}
|
||
}
|
||
|
||
input.addEventListener('input', filter);
|
||
if (clear) clear.addEventListener('click', function () {
|
||
input.value = '';
|
||
filter();
|
||
input.focus();
|
||
});
|
||
})();
|
||
</script>
|
||
</main>
|
||
|
||
<%- include('partials/footer') %>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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.year %>-<%= 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>
|
||
|
||
<!-- Load main JavaScript -->
|
||
<script src="/js/main.js"></script>
|
||
</body>
|
||
</html>
|