Jobangebote: Favoriten-Funktion mit eigenem "Gemerkt"-Tab

Jobangebote lassen sich per Stern merken — eigene Spalte `favorit`,
getrennt von den Bewerbungs-Favoriten. Gemerkte Angebote stehen oben in
der offenen Liste und haben einen eigenen Tab, der Favoriten quer über
alle Status zeigt und ausgeblendet bleibt, solange es keine gibt.

Der Stern wird nur in der Weboberfläche gesetzt; ein erneutes Einspielen
desselben Angebots über die REST-API überschreibt ihn nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 21:45:04 +02:00
co-authored by Claude Opus 4.8
parent cd30af3d69
commit 75813a38a4
5 changed files with 184 additions and 39 deletions
+41 -17
View File
@@ -15,26 +15,35 @@
Zurück zur Übersicht
</a>
<!-- Tabs: open offers vs. taken-over offers -->
<div class="flex flex-wrap items-center gap-2 mb-6">
<a href="/jobangebote" class="px-3 py-1.5 rounded-md text-sm font-medium bg-blue-600 text-white shadow-sm">
Offen (<%= jobangebote.length %>)
</a>
<a href="/jobangebote/uebernommen" class="px-3 py-1.5 rounded-md text-sm font-medium bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
Übernommen (<%= uebernommenCount %>)
</a>
</div>
<!-- Tabs: open offers · favorites · taken-over offers -->
<% var favAnsicht = (typeof ansicht !== 'undefined' && ansicht === 'favoriten'); %>
<%- include('partials/jobangebote-tabs', {
aktiv: favAnsicht ? 'favoriten' : 'offen',
offenCount: offenCount,
favoritenCount: favoritenCount,
uebernommenCount: uebernommenCount,
}) %>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
<%- include('partials/favorit-script') %>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8"<%= favAnsicht ? ' data-fav-card' : '' %>>
<div class="flex flex-wrap items-start justify-between gap-3 mb-2">
<div>
<h2 class="text-lg font-semibold text-gray-800 dark:text-white">Jobangebote</h2>
<h2 class="text-lg font-semibold text-gray-800 dark:text-white"><%= favAnsicht ? 'Gemerkte Jobangebote' : 'Jobangebote' %></h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
Hier landen Stellen, die von Drittanbietersoftware über die
<code class="px-1 py-0.5 rounded bg-gray-100 dark:bg-gray-700">POST /api/v1/joboffers</code>
REST-Schnittstelle eingespielt werden. Ein Angebot lässt sich per Klick
als Bewerbung-Entwurf übernehmen. Hier siehst du nur <strong>offene</strong>
Angebote — bereits übernommene findest du im Tab „Übernommen“.
<% if (favAnsicht) { %>
Angebote, die du dir mit dem Stern gemerkt hast — unabhängig davon, ob sie
noch offen oder bereits übernommen sind. Ein erneuter Klick auf den Stern
entfernt das Angebot wieder aus dieser Liste. Die Merkliste gilt nur für
Jobangebote und ist von den gemerkten Bewerbungen getrennt.
<% } else { %>
Hier landen Stellen, die von Drittanbietersoftware über die
<code class="px-1 py-0.5 rounded bg-gray-100 dark:bg-gray-700">POST /api/v1/joboffers</code>
REST-Schnittstelle eingespielt werden. Ein Angebot lässt sich per Klick
als Bewerbung-Entwurf übernehmen. Hier siehst du nur <strong>offene</strong>
Angebote — bereits übernommene findest du im Tab „Übernommen“.
Mit dem Stern gemerkte Angebote stehen oben.
<% } %>
</p>
</div>
<div class="shrink-0 flex items-center gap-2">
@@ -52,7 +61,9 @@
<% if (jobangebote && jobangebote.length) { %>
<ul class="mt-4 space-y-4">
<% jobangebote.forEach(function(j){ %>
<li class="rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 px-4 py-4">
<li <%= favAnsicht ? 'data-fav-item' : '' %> class="rounded-lg border px-4 py-4 <%= j.favorit
? 'border-amber-300 dark:border-amber-400/40 bg-amber-50/60 dark:bg-amber-500/5'
: 'border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40' %>">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2 mb-1">
@@ -139,6 +150,7 @@
<!-- Actions -->
<div class="shrink-0 flex flex-col gap-2 self-start">
<%- include('partials/favorit-button', { j: j }) %>
<% if (j.verknuepfte_bewerbung_id) { %>
<a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>"
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
@@ -157,6 +169,7 @@
<% } %>
<form action="/jobangebote/<%= j.id %>/delete" method="POST"
onsubmit="return confirm('Jobangebot „<%= j.firma %> · <%= j.stelle %>“ löschen? Es kommt auf die Blacklist und wird nicht erneut eingespielt.');">
<% if (favAnsicht) { %><input type="hidden" name="zurueck" value="favoriten"><% } %>
<button type="submit"
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors w-full">
<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="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>
@@ -173,6 +186,7 @@
Beschreibung / Details bearbeiten
</summary>
<form action="/jobangebote/<%= j.id %>/bearbeiten" method="POST" class="mt-3 space-y-3">
<% if (favAnsicht) { %><input type="hidden" name="zurueck" value="favoriten"><% } %>
<div class="grid gap-3 sm:grid-cols-2">
<label class="block text-xs">
<span class="text-gray-500 dark:text-gray-400">Firma</span>
@@ -228,6 +242,16 @@
</li>
<% }); %>
</ul>
<% } else if (favAnsicht) { %>
<div class="text-center py-12">
<svg class="w-14 h-14 mx-auto text-gray-300 dark:text-gray-600 mb-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.5l2.6 5.27 5.82.85-4.21 4.1.99 5.79-5.2-2.73-5.2 2.73.99-5.79-4.21-4.1 5.82-.85 2.6-5.27z"></path>
</svg>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">Noch keine Jobangebote gemerkt.</p>
<p class="text-xs text-gray-400 dark:text-gray-500">
Klicke bei einem Angebot auf <strong>Merken</strong> — es erscheint dann hier und steht in der offenen Liste ganz oben.
</p>
</div>
<% } else { %>
<div class="text-center py-12">
<svg class="w-14 h-14 mx-auto text-gray-300 dark:text-gray-600 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">