Favorisierte Angebote erscheinen ausschließlich im Gemerkt-Tab und werden aus der Offen-Liste, dem Offen-Zähler (Header-Badge) und dem Tab-Zähler ausgeblendet. Auf der Offen-Seite wird die Zeile beim Merken live entfernt. Co-Authored-By: Claude <noreply@anthropic.com>
326 lines
28 KiB
Plaintext
326 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" data-fav-card data-fav-view="<%= favAnsicht ? 'favoriten' : 'offen' %>">
|
|
<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, die du dir <em>nicht</em> gemerkt hast — bereits übernommene
|
|
findest du im Tab „Übernommen“, gemerkte im Tab „Gemerkt“.
|
|
<% } %>
|
|
</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 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 wird aus der offenen Liste ausgeblendet.
|
|
</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> |