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>
325 lines
28 KiB
Plaintext
325 lines
28 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<%- include('partials/head') %>
|
|
</head>
|
|
<body class="min-h-screen flex flex-col 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="flex-1 container mx-auto px-4 py-8 max-w-5xl">
|
|
<!-- 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>
|
|
|
|
<!-- 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,
|
|
}) %>
|
|
|
|
<%- 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"><%= favAnsicht ? 'Gemerkte Jobangebote' : 'Jobangebote' %></h2>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
<% 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">
|
|
<a href="/blacklist" class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-red-50 dark:bg-red-900/40 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-800 hover:bg-red-100 dark:hover:bg-red-900/60 transition-colors" title="Geblockte Angebote verwalten">
|
|
<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="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"></path></svg>
|
|
Blacklist
|
|
</a>
|
|
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium bg-blue-50 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
|
|
<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="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
<%= jobangebote.length %> Angebot(e)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<% if (jobangebote && jobangebote.length) { %>
|
|
<ul class="mt-4 space-y-4">
|
|
<% jobangebote.forEach(function(j){ %>
|
|
<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">
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= j.status === 'offen' ? 'bg-green-100 text-green-800 dark:bg-green-900/50 dark:text-green-200' : (j.status === 'uebernommen' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-200' : 'bg-gray-200 text-gray-700 dark:bg-gray-600 dark:text-gray-200') %>">
|
|
<%= j.status === 'offen' ? 'Offen' : (j.status === 'uebernommen' ? 'Übernommen' : (j.status === 'abgelehnt' ? 'Abgelehnt' : j.status)) %>
|
|
</span>
|
|
<% if (j.quelle && j.quelle !== 'drittanbieter') { %>
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200">Quelle: <%= j.quelle %></span>
|
|
<% } %>
|
|
<% if (j.art) { %>
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"><%= j.art %></span>
|
|
<% } %>
|
|
</div>
|
|
<p class="text-base font-semibold text-gray-800 dark:text-gray-100 break-words"><%= j.firma %></p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-200 break-words"><%= j.stelle %></p>
|
|
<% if (j.labelsArr && j.labelsArr.length) { %>
|
|
<div class="flex flex-wrap gap-1 mt-1.5"><%- include('partials/labelChips', { labels: j.labelsArr }) %></div>
|
|
<% } %>
|
|
<% if (j.ort || j.gehalt) { %>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
<% if (j.ort) { %><%= j.ort %><% } %>
|
|
<% if (j.ort && j.gehalt) { %> · <% } %>
|
|
<% if (j.gehalt) { %><%= j.gehalt %><% } %>
|
|
</p>
|
|
<% } %>
|
|
<% if (j.adresse) { %>
|
|
<p class="text-sm mt-1 flex items-start gap-1.5 min-w-0">
|
|
<span class="text-gray-500 dark:text-gray-400 shrink-0">Anschrift:</span>
|
|
<span class="text-gray-700 dark:text-gray-200 break-words whitespace-pre-line"><%= j.adresse %></span>
|
|
</p>
|
|
<% } %>
|
|
<% if (j.ansprechpartner) { %>
|
|
<p class="text-sm mt-1 flex items-start gap-1.5 min-w-0">
|
|
<span class="text-gray-500 dark:text-gray-400 shrink-0">Ansprechpartner:</span>
|
|
<span class="text-gray-700 dark:text-gray-200 break-words"><%= j.ansprechpartner %></span>
|
|
</p>
|
|
<% } %>
|
|
<% if (j.anzeige_datum) { %>
|
|
<p class="text-sm mt-1 flex items-center gap-1.5">
|
|
<span class="text-gray-500 dark:text-gray-400 shrink-0">Anzeige:</span>
|
|
<span class="inline-flex items-center gap-1 text-gray-700 dark:text-gray-200">
|
|
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
<%= new Date(j.anzeige_datum).toLocaleDateString('de-DE') %>
|
|
</span>
|
|
</p>
|
|
<% } %>
|
|
<% if (j.kontakt_email) { %>
|
|
<p class="text-sm mt-1 flex items-center gap-1.5 min-w-0">
|
|
<span class="text-gray-500 dark:text-gray-400 shrink-0">Kontakt:</span>
|
|
<a href="mailto:<%= j.kontakt_email %>"
|
|
class="inline-flex items-center gap-1 text-blue-600 dark:text-blue-400 hover:underline break-all">
|
|
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
|
|
<%= j.kontakt_email %>
|
|
</a>
|
|
</p>
|
|
<% } %>
|
|
<% if (j.beschreibung) { %>
|
|
<p class="joboffer-desc whitespace-pre-wrap break-words text-sm leading-relaxed text-gray-700 dark:text-gray-300 mt-2 max-h-32 overflow-hidden"><%= j.beschreibung %></p>
|
|
<% if (j.beschreibung.length > 400) { %>
|
|
<button type="button" class="desc-toggle text-xs text-blue-600 hover:text-blue-800 dark:text-blue-400 hover:underline mt-1" data-expanded="false">Vollständig anzeigen</button>
|
|
<% } %>
|
|
<% } %>
|
|
<% if (j.quelle_url) { %>
|
|
<a href="<%= safeUrl(j.quelle_url) %>" target="_blank" rel="noopener noreferrer"
|
|
class="inline-flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline mt-2 break-all">
|
|
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path></svg>
|
|
<span class="truncate"><%= j.quelle_url %></span>
|
|
</a>
|
|
<% } %>
|
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-2 flex flex-wrap gap-x-3 gap-y-1">
|
|
<% if (j.verknuepfte_bewerbung_id && j.bewerbung_datum) { %>
|
|
<span title="Datum der aus diesem Angebot erstellten Bewerbung">
|
|
Bewerbung vom: <span class="text-gray-500 dark:text-gray-400"><%= new Date(j.bewerbung_datum).toLocaleDateString('de-DE') %></span>
|
|
</span>
|
|
<% } %>
|
|
<span title="Wann das Angebot über die REST-API eingespielt wurde">
|
|
Einspielung: <span class="text-gray-500 dark:text-gray-400"><%= j.created_at ? new Date(j.created_at + 'Z').toLocaleString('de-DE') : '' %></span>
|
|
</span>
|
|
<% if (j.verknuepfte_bewerbung_id) { %>
|
|
<span>· <a href="/bewerbung/<%= j.verknuepfte_bewerbung_id %>" class="text-blue-600 dark:text-blue-400 hover:underline">Bewerbung #<%= j.verknuepfte_bewerbung_id %></a></span>
|
|
<% } %>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 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">
|
|
<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="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
Zur Bewerbung
|
|
</a>
|
|
<% } else { %>
|
|
<form action="/jobangebote/<%= j.id %>/uebernehmen" method="POST">
|
|
<button type="submit"
|
|
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors w-full"
|
|
title="Aus diesem Angebot einen Bewerbungs-Entwurf anlegen">
|
|
<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 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
|
Als Bewerbung übernehmen
|
|
</button>
|
|
</form>
|
|
<% } %>
|
|
<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>
|
|
Löschen
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit form: paste/adjust the full job description (any length). -->
|
|
<details class="mt-3 border-t border-gray-200 dark:border-gray-600 pt-3">
|
|
<summary class="cursor-pointer inline-flex items-center gap-1.5 text-xs text-blue-600 dark:text-blue-400 hover:underline select-none">
|
|
<svg class="w-3.5 h-3.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>
|
|
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>
|
|
<input name="firma" value="<%= j.firma %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Stelle</span>
|
|
<input name="stelle" value="<%= j.stelle %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Ort</span>
|
|
<input name="ort" value="<%= j.ort || '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Ansprechpartner</span>
|
|
<input name="ansprechpartner" value="<%= j.ansprechpartner || '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs sm:col-span-2">
|
|
<span class="text-gray-500 dark:text-gray-400">Anschrift <span class="text-gray-400">(Straße, Hausnummer, PLZ, Ort — wird bei „Übernehmen“ in die KI-Notizen übernommen)</span></span>
|
|
<input name="adresse" value="<%= j.adresse || '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Gehalt</span>
|
|
<input name="gehalt" value="<%= j.gehalt || '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Kontakt-E-Mail</span>
|
|
<input name="kontakt_email" type="email" value="<%= j.kontakt_email || '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Anzeige-Datum</span>
|
|
<input name="anzeige_datum" type="date" value="<%= j.anzeige_datum ? new Date(j.anzeige_datum).toISOString().split('T')[0] : '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
<label class="block text-xs sm:col-span-2">
|
|
<span class="text-gray-500 dark:text-gray-400">Quelle-URL</span>
|
|
<input name="quelle_url" value="<%= j.quelle_url || '' %>" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-2 py-1.5 text-sm">
|
|
</label>
|
|
</div>
|
|
<label class="block text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Stellenbeschreibung <span class="text-gray-400">(beliebig lang — wird bei „Übernehmen“ als Stellenbeschreibung für die KI mitgenommen)</span></span>
|
|
<textarea name="beschreibung" rows="10" class="mt-1 w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm leading-relaxed" placeholder="Vollständige Stellenbeschreibung hier einfügen …"><%= j.beschreibung || '' %></textarea>
|
|
</label>
|
|
<div class="text-xs">
|
|
<span class="text-gray-500 dark:text-gray-400">Labels</span>
|
|
<div class="mt-1"><%- include('partials/labelPicker', { labelOptions: labelOptions, selected: j.labelsArr }) %></div>
|
|
</div>
|
|
<button type="submit" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded-md 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="M5 13l4 4L19 7"></path></svg>
|
|
Speichern
|
|
</button>
|
|
</form>
|
|
</details>
|
|
</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">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
|
</svg>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">Noch keine Jobangebote vorhanden.</p>
|
|
<p class="text-xs text-gray-400 dark:text-gray-500">
|
|
Drittanbietersoftware kann Angebote über
|
|
<code class="px-1 py-0.5 rounded bg-gray-100 dark:bg-gray-700">POST /api/v1/joboffers</code>
|
|
einspielen.
|
|
</p>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
|
|
<!-- Ingestion hint / mini example -->
|
|
<details class="bg-white dark:bg-gray-800 rounded-lg shadow-md px-6 py-4 text-sm">
|
|
<summary class="cursor-pointer text-gray-600 dark:text-gray-300 font-medium">
|
|
Wie werden Jobangebote eingespielt?
|
|
</summary>
|
|
<div class="mt-3 text-gray-600 dark:text-gray-400 space-y-2">
|
|
<p>Jedes Angebot hat zwei Datumswerte:</p>
|
|
<ul class="list-disc pl-5 space-y-1 text-xs">
|
|
<li><strong>Anzeige</strong> (<code>anzeige_datum</code>) — wann die Stellenanzeige ursprünglich erschienen ist; wird vom Einsender im Request mitgegeben.</li>
|
|
<li><strong>Einspielung</strong> (<code>created_at</code>) — wann das Angebot hier im Tracker über die REST-API angekommen ist; wird automatisch vom Server gesetzt, <em>nicht</em> im Request gesendet.</li>
|
|
</ul>
|
|
<p>Angebot über die REST-API anlegen (API-Key im Header <code>X-API-Key</code>):</p>
|
|
<pre class="overflow-x-auto rounded-md bg-gray-900 text-gray-100 text-xs px-4 py-3"><code>curl -X POST https://<host>/api/v1/joboffers \
|
|
-H "X-API-Key: $API_TOKEN" -H "Content-Type: application/json" \
|
|
-d '{
|
|
"external_id": "job-12345",
|
|
"quelle": "mein-crm",
|
|
"firma": "Acme GmbH",
|
|
"stelle": "Softwareentwickler (m/w/d)",
|
|
"ort": "Berlin",
|
|
"gehalt": "55.000 €",
|
|
"beschreibung": "…",
|
|
"quelle_url": "https://acme.example/job/12345",
|
|
"art": "Online-Portal",
|
|
"anzeige_datum": "2026-07-01",
|
|
"kontakt_email": "bewerbung@acme.example"
|
|
// "created_at" (Einspielung) NICHT mitsenden — wird vom Server automatisch gesetzt.
|
|
}'</code></pre>
|
|
<p class="text-xs">Wiederholtes Senden mit derselben <code>quelle</code> + <code>external_id</code> aktualisiert das Angebot (Upsert). Die Doku/Swagger-UI liegt unter <a href="/swagger" class="text-blue-600 dark:text-blue-400 hover:underline">/swagger</a>.</p>
|
|
</div>
|
|
</details>
|
|
</main>
|
|
|
|
<%- include('partials/footer') %>
|
|
|
|
<script>
|
|
(function () {
|
|
const cy = document.getElementById('currentYear');
|
|
if (cy) cy.textContent = new Date().getFullYear();
|
|
|
|
// Expand/collapse the full description for each card.
|
|
document.querySelectorAll('.desc-toggle').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var body = btn.previousElementSibling;
|
|
if (!body || !body.classList.contains('joboffer-desc')) return;
|
|
var expanded = btn.dataset.expanded === 'true';
|
|
body.classList.toggle('max-h-32', expanded);
|
|
body.classList.toggle('overflow-hidden', expanded);
|
|
btn.dataset.expanded = expanded ? 'false' : 'true';
|
|
btn.textContent = expanded ? 'Vollständig anzeigen' : 'Weniger anzeigen';
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html> |