Files
jobbi-bewerbung/views/blacklist.ejs
T
thomasandClaude Opus 4.8 b269dcaecc Footer immer am Seitenende (Sticky) und Text auf "Bewerbungssystem"
Body als Flex-Column, main mit flex-1 drueckt den Footer nach unten.
Footer-Jahr serverseitig gerendert (immer aktuell).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:40:55 +02:00

197 lines
13 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="/jobangebote" 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 zu den Jobangeboten
</a>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
<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">Blacklist</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 max-w-2xl">
Angebote auf der Blacklist werden nie (erneut) in die Jobangebote
aufgenommen. Ein <strong>gelöschtes</strong> Jobangebot landet automatisch
hier, damit dieselbe Stelle nicht doppelt auftaucht. Du kannst auch manuell
eine URL, eine ganze Domain, eine Firma oder eine konkrete Firma + Stelle
blockieren. Einträge lassen sich jederzeit wieder entfernen.
</p>
</div>
<span class="shrink-0 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">
<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>
<%= eintraege.length %> Eintrag/Einträge
</span>
</div>
<% if (eintraege && eintraege.length) { %>
<ul class="mt-4 space-y-3">
<% eintraege.forEach(function(e){
var typLabel = { url: 'URL', domain: 'Domain', firma: 'Firma', firma_stelle: 'Firma + Stelle', auto: 'Automatisch (gelöscht)' }[e.typ] || e.typ;
var typClass = e.typ === 'auto'
? 'bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-200'
: 'bg-purple-100 text-purple-800 dark:bg-purple-900/50 dark:text-purple-200';
var wert = '';
if (e.typ === 'url') wert = e.quelle_url || e.url_norm || '';
else if (e.typ === 'domain') wert = e.domain || '';
else if (e.typ === 'firma') wert = e.firma || e.firma_norm || '';
else if (e.typ === 'firma_stelle') wert = [e.firma, e.stelle].filter(Boolean).join(' · ');
else wert = [e.firma, e.stelle].filter(Boolean).join(' · ') || e.quelle_url || e.url_norm || e.domain || '';
%>
<li class="rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 px-4 py-3">
<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 <%= typClass %>"><%= typLabel %></span>
<% if (e.typ === 'auto' && e.quelle_url) { %>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300">URL erfasst</span>
<% } %>
<% if (e.external_id) { %>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-200 text-gray-600 dark:bg-gray-700 dark:text-gray-300">ext. ID</span>
<% } %>
</div>
<p class="text-sm font-medium text-gray-800 dark:text-gray-100 break-all"><%= wert || '(kein Wert)' %></p>
<% if (e.typ === 'auto' && e.quelle_url && wert !== e.quelle_url) { %>
<p class="text-xs text-gray-500 dark:text-gray-400 break-all mt-0.5"><%= e.quelle_url %></p>
<% } %>
<% if (e.grund) { %>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Grund: <%= e.grund %></p>
<% } %>
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
Hinzugefügt: <%= e.created_at ? new Date(e.created_at + 'Z').toLocaleString('de-DE') : '' %>
</p>
</div>
<form action="/blacklist/<%= e.id %>/delete" method="POST"
onsubmit="return confirm('Blacklist-Eintrag entfernen? Das Angebot kann danach wieder eingespielt werden.');">
<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">
<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>
Entfernen
</button>
</form>
</div>
</li>
<% }); %>
</ul>
<% } else { %>
<div class="text-center py-10">
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<p class="text-sm text-gray-500 dark:text-gray-400">Die Blacklist ist leer.</p>
</div>
<% } %>
</div>
<!-- Add a manual blacklist entry -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-base font-semibold text-gray-800 dark:text-white mb-4">Eintrag manuell hinzufügen</h3>
<form action="/blacklist" method="POST" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="bl-typ">Typ</label>
<select id="bl-typ" name="typ"
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
<option value="url">URL blockieren</option>
<option value="domain">Ganze Domain blockieren</option>
<option value="firma">Firma komplett blockieren</option>
<option value="firma_stelle">Firma + Stelle blockieren</option>
</select>
</div>
<div data-when="url domain firma">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="bl-wert">
<span data-label="url">URL</span>
<span data-label="domain" class="hidden">Domain (z. B. beispiel.de)</span>
<span data-label="firma" class="hidden">Firmenname</span>
</label>
<input id="bl-wert" name="wert" type="text" autocomplete="off"
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm"
placeholder="https://…">
</div>
<div data-when="firma_stelle" class="hidden grid gap-4 sm:grid-cols-3">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="bl-firma">Firma</label>
<input id="bl-firma" name="firma" type="text"
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="bl-stelle">Stelle</label>
<input id="bl-stelle" name="stelle" type="text"
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="bl-ort">Ort <span class="text-gray-400">(optional)</span></label>
<input id="bl-ort" name="ort" type="text"
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="bl-grund">Grund <span class="text-gray-400">(optional)</span></label>
<input id="bl-grund" name="grund" type="text"
class="w-full rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-sm"
placeholder="z. B. Zeitarbeit, unseriös, kein Interesse …">
</div>
<button type="submit"
class="inline-flex items-center gap-2 px-4 py-2 text-sm 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="M12 4v16m8-8H4"></path></svg>
Zur Blacklist hinzufügen
</button>
</form>
</div>
</main>
<%- include('partials/footer') %>
<script>
(function () {
const root = document.documentElement;
const dm = localStorage.getItem('darkMode');
if (dm === 'enabled' || (!dm && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
root.classList.add('dark');
}
const toggle = document.getElementById('darkModeToggle');
const sun = document.getElementById('sunIcon');
const moon = document.getElementById('moonIcon');
function sync() {
const d = root.classList.contains('dark');
if (sun) sun.classList.toggle('hidden', d);
if (moon) moon.classList.toggle('hidden', !d);
}
sync();
if (toggle) toggle.addEventListener('click', () => {
root.classList.toggle('dark');
localStorage.setItem('darkMode', root.classList.contains('dark') ? 'enabled' : 'disabled');
sync();
});
const cy = document.getElementById('currentYear');
if (cy) cy.textContent = new Date().getFullYear();
// Show only the fields relevant to the chosen blacklist type.
const typ = document.getElementById('bl-typ');
function applyTyp() {
const v = typ.value;
document.querySelectorAll('[data-when]').forEach(function (el) {
el.classList.toggle('hidden', el.getAttribute('data-when').split(' ').indexOf(v) === -1);
});
document.querySelectorAll('[data-label]').forEach(function (el) {
el.classList.toggle('hidden', el.getAttribute('data-label') !== v);
});
}
if (typ) { typ.addEventListener('change', applyTyp); applyTyp(); }
})();
</script>
</body>
</html>