Startseite: Bewerbungsliste als klappbare Sektionen (eine Zeile pro Bewerbung)
Tabelle auf Grid-Layout umgebaut. Status-Gruppen und der Detailbereich (Verlauf/Notizen) je Bewerbung sind animiert zuklappbar, Default zugeklappt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+216
-163
@@ -177,27 +177,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Applications Table -->
|
||||
<!-- Applications -->
|
||||
<style>
|
||||
/* Shared column grid – used by the header labels and every application line */
|
||||
.app-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.25rem 5.5rem minmax(7rem, 1.6fr) minmax(8rem, 2fr) 5.5rem 8.5rem 4.5rem;
|
||||
gap: 0 0.75rem;
|
||||
align-items: center;
|
||||
}
|
||||
/* Smooth height collapse via the grid-template-rows 0fr → 1fr trick */
|
||||
.collapsible {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 0.32s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.collapsible > .collapsible-inner {
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
.collapsible.open {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.chevron {
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
.chevron.rotate {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
<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>
|
||||
<div class="min-w-[760px]">
|
||||
<!-- Column labels -->
|
||||
<div class="app-grid px-6 py-3 bg-gray-50 dark:bg-gray-700 text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
<span></span>
|
||||
<span>Datum</span>
|
||||
<span>Firma</span>
|
||||
<span>Stelle</span>
|
||||
<span>Art</span>
|
||||
<span>Status</span>
|
||||
<span>Aktionen</span>
|
||||
</div>
|
||||
<% if (applications.length > 0) { %>
|
||||
<%
|
||||
// Status-Reihenfolge synchron zu public/js/main.js halten
|
||||
@@ -223,172 +243,205 @@
|
||||
...Object.keys(groups).filter(k => !statusOrder.includes(k))
|
||||
];
|
||||
%>
|
||||
<div id="appList">
|
||||
<% 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>
|
||||
<section class="status-group border-t border-gray-200 dark:border-gray-700">
|
||||
<!-- Klappbarer Status-Kopf (Gesendet, Eingangsbestätigung, …) -->
|
||||
<button type="button" class="group-toggle w-full flex items-center gap-3 px-6 py-3 bg-gray-100 dark:bg-gray-900/40 hover:bg-gray-200/70 dark:hover:bg-gray-900/60 transition-colors text-left">
|
||||
<svg class="chevron w-4 h-4 flex-shrink-0 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<span class="inline-block w-2.5 h-2.5 rounded-full flex-shrink-0 <%= 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>
|
||||
</button>
|
||||
<div class="group-body collapsible">
|
||||
<div class="collapsible-inner">
|
||||
<% 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;
|
||||
%>
|
||||
<div class="app-row border-t border-gray-100 dark:border-gray-700/60 <%= hatDetails ? 'has-details' : '' %>" data-search="<%= ((app.firma || '') + ' ' + (app.stelle || '')).toLowerCase().replace(/"/g, '') %>">
|
||||
<!-- Eine Zeile pro Bewerbung -->
|
||||
<div class="app-line app-grid px-6 py-3 text-sm transition-colors <%= hatDetails ? 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50' : '' %>">
|
||||
<span class="flex items-center justify-center">
|
||||
<% if (hatDetails) { %>
|
||||
<svg class="chevron w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<% } %>
|
||||
</span>
|
||||
<span class="whitespace-nowrap text-gray-900 dark:text-white"><%= new Date(app.datum).toLocaleDateString('de-DE') %></span>
|
||||
<span class="min-w-0 truncate font-medium text-gray-900 dark:text-white" title="<%= app.firma %>"><%= app.firma %></span>
|
||||
<span class="min-w-0 truncate text-gray-900 dark:text-white" title="<%= app.stelle %>"><%= app.stelle %></span>
|
||||
<span class="min-w-0 truncate text-gray-500 dark:text-gray-400"><%= app.art || '-' %></span>
|
||||
<span class="min-w-0">
|
||||
<span class="inline-block max-w-full truncate align-middle 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>
|
||||
</span>
|
||||
<span class="app-actions flex items-center gap-3">
|
||||
<a href="/bewerbung/<%= app.id %>"
|
||||
class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
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 type="button"
|
||||
class="delete-btn text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
|
||||
data-id="<%= app.id %>" aria-label="Löschen">
|
||||
<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>
|
||||
</span>
|
||||
</div>
|
||||
<% if (hatDetails) { %>
|
||||
<!-- Klappbarer Detailbereich unter der Zeile -->
|
||||
<div class="app-details collapsible">
|
||||
<div class="collapsible-inner">
|
||||
<div class="px-6 pb-5 pt-1 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>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</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>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<% }); %>
|
||||
<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>
|
||||
</div>
|
||||
<div id="searchNoResults" class="px-6 py-12 text-center text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700" style="display:none">
|
||||
<p>Keine Firma gefunden für „<span id="searchNoResultsTerm"></span>".</p>
|
||||
</div>
|
||||
<% } 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>
|
||||
<div class="px-6 py-12 text-center text-gray-500 dark:text-gray-400 border-t border-gray-200 dark:border-gray-700">
|
||||
<p>Keine Bewerbungen gefunden.</p>
|
||||
<p class="mt-2 text-sm">Klicken Sie auf "Bewerbung hinzufügen", um Ihre erste Bewerbung einzutragen.</p>
|
||||
</div>
|
||||
<% } %>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Live company search — filters the applications table by firma/stelle.
|
||||
(function () {
|
||||
function qsa(sel, ctx) {
|
||||
return Array.prototype.slice.call((ctx || document).querySelectorAll(sel));
|
||||
}
|
||||
|
||||
// Öffnet/schließt einen klappbaren Bereich und dreht sein Chevron mit.
|
||||
function setOpen(collapsible, chevron, open) {
|
||||
if (!collapsible) return;
|
||||
collapsible.classList.toggle('open', open);
|
||||
if (chevron) chevron.classList.toggle('rotate', open);
|
||||
}
|
||||
|
||||
// Status-Gruppen (Gesendet, Eingangsbestätigung …) auf-/zuklappen.
|
||||
qsa('.group-toggle').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var body = btn.parentElement.querySelector('.group-body');
|
||||
var chev = btn.querySelector('.chevron');
|
||||
setOpen(body, chev, !body.classList.contains('open'));
|
||||
});
|
||||
});
|
||||
|
||||
// Detailbereich einer einzelnen Bewerbung auf-/zuklappen.
|
||||
qsa('.app-row.has-details').forEach(function (row) {
|
||||
var line = row.querySelector('.app-line');
|
||||
line.addEventListener('click', function (e) {
|
||||
if (e.target.closest('.app-actions')) return; // Bearbeiten/Löschen nicht abfangen
|
||||
var details = row.querySelector('.app-details');
|
||||
var chev = line.querySelector('.chevron');
|
||||
setOpen(details, chev, !details.classList.contains('open'));
|
||||
});
|
||||
});
|
||||
|
||||
// ---- Live-Firmensuche: filtert die Bewerbungsliste nach Firma/Stelle ----
|
||||
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 sections = qsa('.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);
|
||||
var anyVisible = false;
|
||||
|
||||
appRows.forEach(function (tb) {
|
||||
var match = !q || (tb.dataset.search || '').indexOf(q) !== -1;
|
||||
tb.style.display = match ? '' : 'none';
|
||||
});
|
||||
sections.forEach(function (sec) {
|
||||
var rows = qsa('.app-row', sec);
|
||||
var visible = 0;
|
||||
rows.forEach(function (row) {
|
||||
var match = !q || (row.dataset.search || '').indexOf(q) !== -1;
|
||||
row.style.display = match ? '' : 'none';
|
||||
if (match) visible++;
|
||||
});
|
||||
if (visible) anyVisible = true;
|
||||
|
||||
// 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]');
|
||||
var countEl = sec.querySelector('[data-group-count]');
|
||||
if (countEl) countEl.textContent = visible;
|
||||
sec.style.display = (visible === 0 && q) ? 'none' : '';
|
||||
|
||||
// Während der Suche passende Gruppen aufklappen, nach dem Löschen alles wieder zu.
|
||||
var body = sec.querySelector('.group-body');
|
||||
var chev = sec.querySelector('.group-toggle .chevron');
|
||||
setOpen(body, chev, !!q && visible > 0);
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user