Files
jobbi-bewerbung/views/aktivitaeten.ejs
T
thomasandClaude 7c92f23351 Aktivitäten-Feed: empfangene und gesendete E-Mails einbeziehen
Der Aktivitäten-Feed (Startseite, /aktivitaeten, /statistik) führt Status-
änderungen und E-Mails per UNION zusammen, sortiert nach Erfassungszeit.
E-Mail-Einträge bekommen einen eigenen Farbpunkt (blau = erhalten, sky =
gesendet); ohne verknüpfte Bewerbung wird Absender bzw. Empfänger als
Kopfzeile gezeigt. Das UNION steckt in einem Subselect, weil SQLite ORDER BY
an einem Compound-SELECT keine Ergebnisspalten nach Namen aufloest. Die
Paginierung von /aktivitaeten zaehlt jetzt Statusaenderungen + E-Mails.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 15:03:20 +02:00

169 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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-4xl">
<%
// Farben synchron zur Startseite (views/index.ejs) halten.
const statusDot = {
'Entwurf': 'bg-purple-500',
'Gesendet': 'bg-indigo-500',
'Eingangsbestätigung': 'bg-blue-500',
'In Bearbeitung': 'bg-teal-500',
'Interessiert': 'bg-pink-500',
'Warten auf Rückmeldung': 'bg-orange-500',
'Warten auf meine Antwort': 'bg-fuchsia-500',
'Vorstellungsgespräch': 'bg-yellow-500',
'Vertragsverhandlung': 'bg-lime-500',
'Einstellung': 'bg-green-500',
'Absage': 'bg-red-500',
'Absage von meiner Seite': 'bg-rose-500',
'Keine Rückmeldung': 'bg-gray-400'
};
// Welche Seitenzahlen die Blätterleiste zeigt: erste, letzte, die aktuelle
// und je eine daneben — dazwischen Auslassungen (null). Bei wenigen Seiten
// stehen ohnehin alle da.
const zahlen = [];
for (let i = 1; i <= seiten; i++) {
const nah = Math.abs(i - seite) <= 1;
const rand = (i === 1 || i === seiten);
if (nah || rand) {
if (zahlen.length && zahlen[zahlen.length - 1] !== null && i - zahlen[zahlen.length - 1] > 1) zahlen.push(null);
zahlen.push(i);
}
}
const von = gesamt === 0 ? 0 : (seite - 1) * proSeite + 1;
const bis = Math.min(seite * proSeite, gesamt);
%>
<div class="mb-6 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
<div>
<h1 class="text-2xl font-bold text-gray-800 dark:text-white">Aktivitäten</h1>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
Jede Statusänderung, über alle Bewerbungen hinweg die jüngste zuerst.
</p>
</div>
<a href="/" class="text-sm text-blue-600 dark:text-blue-400 hover:underline">Zurück zu den Bewerbungen</a>
</div>
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md">
<div class="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<p class="text-sm text-gray-600 dark:text-gray-300">
<% if (gesamt) { %>
<span class="font-semibold text-gray-800 dark:text-gray-100"><%= von %><%= bis %></span>
von <span class="font-semibold text-gray-800 dark:text-gray-100"><%= gesamt %></span>
<%= gesamt === 1 ? 'Eintrag' : 'Einträgen' %>
<% } else { %>
Keine Einträge
<% } %>
</p>
<% if (seiten > 1) { %>
<p class="text-sm text-gray-400 dark:text-gray-500">Seite <%= seite %> von <%= seiten %></p>
<% } %>
</div>
<% if (aktivitaet.length) { %>
<ul class="divide-y divide-gray-100 dark:divide-gray-700/60">
<% aktivitaet.forEach(function (a) {
var dotCls = a.typ === 'email'
? (a.direction === 'out' ? 'bg-sky-500' : 'bg-blue-500')
: (statusDot[a.status] || 'bg-gray-300');
var titel = '', titelSub = '';
if (a.bewerbung_id) {
titel = a.firma || '(ohne Firma)';
titelSub = a.stelle || '';
} else if (a.typ === 'email') {
titel = a.direction === 'out' ? ('An: ' + (a.to_addr || '?')) : (a.from_addr || a.subject || 'E-Mail');
} else {
titel = 'Gelöschte Bewerbung';
}
%>
<li>
<% if (a.bewerbung_id) { %>
<a href="/bewerbung/<%= a.bewerbung_id %>" class="flex items-start gap-3 px-6 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
<% } else { %>
<div class="flex items-start gap-3 px-6 py-3">
<% } %>
<span class="mt-1.5 inline-block w-2.5 h-2.5 rounded-full shrink-0 <%= dotCls %>"></span>
<span class="min-w-0 flex-1">
<span class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
<span class="text-sm font-medium <%= a.bewerbung_id ? 'text-gray-900 dark:text-white' : 'text-gray-500 dark:text-gray-400' %> break-words"><%= titel %></span>
<% if (titelSub) { %>
<span class="text-sm text-gray-500 dark:text-gray-400 break-words">· <%= titelSub %></span>
<% } %>
</span>
<span class="mt-0.5 flex flex-wrap items-baseline gap-x-2 text-sm">
<span class="font-medium text-gray-700 dark:text-gray-200"><%= a.status %></span>
<% if (a.datum) { %>
<span class="text-gray-400 dark:text-gray-500 whitespace-nowrap">· <%= new Date(a.datum).toLocaleDateString('de-DE') %></span>
<% } %>
<% if (a.kommentar && a.kommentar.trim()) { %>
<span class="text-gray-500 dark:text-gray-400 break-words">· <%= a.kommentar %></span>
<% } %>
</span>
</span>
<% if (a.bewerbung_id) { %></a><% } else { %></div><% } %>
</li>
<% }); %>
</ul>
<% } else { %>
<div class="px-6 py-16 text-center text-sm text-gray-500 dark:text-gray-400">
Noch keine Aktivitäten — Statusänderungen und E-Mails erscheinen hier.
</div>
<% } %>
<% if (seiten > 1) { %>
<!-- Blätterleiste: echte Links (kein JS), damit Zurück-Taste, Lesezeichen
und das Öffnen in einem neuen Tab funktionieren. -->
<nav class="flex flex-wrap items-center justify-center gap-1 px-6 py-4 border-t border-gray-200 dark:border-gray-700" aria-label="Seiten">
<% if (seite > 1) { %>
<a href="/aktivitaeten?seite=<%= seite - 1 %>" rel="prev"
class="inline-flex items-center gap-1 px-3 py-1.5 text-sm rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 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="M15 19l-7-7 7-7"></path></svg>
Zurück
</a>
<% } else { %>
<span class="inline-flex items-center gap-1 px-3 py-1.5 text-sm rounded-md border border-gray-200 dark:border-gray-700 text-gray-300 dark:text-gray-600 cursor-not-allowed">
<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="M15 19l-7-7 7-7"></path></svg>
Zurück
</span>
<% } %>
<% zahlen.forEach(function (n) { %>
<% if (n === null) { %>
<span class="px-2 text-sm text-gray-400 dark:text-gray-600">…</span>
<% } else if (n === seite) { %>
<span aria-current="page"
class="min-w-[2.25rem] text-center px-2 py-1.5 text-sm font-medium rounded-md bg-blue-600 text-white"><%= n %></span>
<% } else { %>
<a href="/aktivitaeten?seite=<%= n %>"
class="min-w-[2.25rem] text-center px-2 py-1.5 text-sm rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"><%= n %></a>
<% } %>
<% }); %>
<% if (seite < seiten) { %>
<a href="/aktivitaeten?seite=<%= seite + 1 %>" rel="next"
class="inline-flex items-center gap-1 px-3 py-1.5 text-sm rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
Weiter
<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="M9 5l7 7-7 7"></path></svg>
</a>
<% } else { %>
<span class="inline-flex items-center gap-1 px-3 py-1.5 text-sm rounded-md border border-gray-200 dark:border-gray-700 text-gray-300 dark:text-gray-600 cursor-not-allowed">
Weiter
<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="M9 5l7 7-7 7"></path></svg>
</span>
<% } %>
</nav>
<% } %>
</section>
</main>
<%- include('partials/footer') %>
</body>
</html>