Startseite: Letzte Aktivitaeten und Gesamtzahl der Bewerbungen
Unter der Liste stehen jetzt die acht juengsten Statusaenderungen quer ueber alle Bewerbungen - mit Firma, Stelle, Status, Datum und Kommentar, verlinkt auf die Bewerbung. Sortiert nach Erfassungszeit statt nach fachlichem Datum, damit ein nachgetragener alter Eintrag nicht oben steht. Der Kopf der Listen-Karte nennt die Gesamtzahl. Er sitzt bewusst ausserhalb des md:min-w-Rahmens, damit er auf dem Handy nicht horizontal mitscrollt. Beides verschwindet waehrend einer Suche, wie Liste und Termine auch. Die Status-Farbkarte ist eine Ebene hoeher gewandert, weil Liste und Aktivitaeten sie jetzt beide brauchen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1647,6 +1647,20 @@ initializeDatabase().then(async () => {
|
||||
GROUP BY status ORDER BY count DESC
|
||||
`, [U]);
|
||||
|
||||
// Letzte Aktivitäten für die Startseite: die jüngsten Statusänderungen, quer
|
||||
// über alle Bewerbungen. Sortiert nach Erfassungszeit (created_at), nicht nach
|
||||
// dem fachlichen Datum — sonst würde ein nachgetragener alter Eintrag oben
|
||||
// stehen, obwohl gerade etwas anderes passiert ist.
|
||||
const aktivitaet = await dbAll(`
|
||||
SELECT sv.datum, sv.status, sv.kommentar,
|
||||
b.id AS bewerbung_id, b.firma, b.stelle
|
||||
FROM status_verlauf sv
|
||||
LEFT JOIN bewerbungen b ON b.id = sv.bewerbung_id AND b.user_id = sv.user_id
|
||||
WHERE sv.user_id = ?
|
||||
ORDER BY datetime(sv.created_at) DESC, sv.id DESC
|
||||
LIMIT 8
|
||||
`, [U]);
|
||||
|
||||
// Get available months/years for filter
|
||||
const availableMonths = await dbAll(`
|
||||
SELECT DISTINCT strftime("%Y-%m", datum) as yearmonth,
|
||||
@@ -1683,6 +1697,7 @@ initializeDatabase().then(async () => {
|
||||
availableMonths,
|
||||
exportMonths,
|
||||
currentFilter: { month, year },
|
||||
aktivitaet,
|
||||
kommendeTermine,
|
||||
caldavTz: caldav.TZ,
|
||||
artOptions: ART_OPTIONS,
|
||||
|
||||
+79
-14
@@ -130,21 +130,9 @@
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
</style>
|
||||
<div id="listCard" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-x-auto">
|
||||
<div class="md:min-w-[760px]">
|
||||
<!-- Column labels (desktop only — on phones the rows become stacked cards) -->
|
||||
<div class="app-head app-grid px-6 py-3 bg-gray-50 dark:bg-gray-700 text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
<span></span>
|
||||
<span>Datum</span>
|
||||
<span>Firma</span>
|
||||
<span>Stelle</span>
|
||||
<span>Art</span>
|
||||
<span>Status</span>
|
||||
<span>Aktionen</span>
|
||||
</div>
|
||||
<% if (applications.length > 0) { %>
|
||||
<%
|
||||
// Status-Reihenfolge synchron zu public/js/main.js halten
|
||||
// Status-Reihenfolge synchron zu public/js/main.js halten. Steht hier oben,
|
||||
// weil sowohl die Liste als auch die Aktivitäten weiter unten sie brauchen.
|
||||
const statusOrder = ['Entwurf', 'Gesendet', 'Eingangsbestätigung',
|
||||
'In Bearbeitung', 'Interessiert', 'Warten auf Rückmeldung', 'Warten auf meine Antwort',
|
||||
'Vorstellungsgespräch', 'Vertragsverhandlung', 'Einstellung', 'Absage', 'Absage von meiner Seite', 'Keine Rückmeldung'];
|
||||
@@ -164,6 +152,30 @@
|
||||
'Keine Rückmeldung': 'bg-gray-400',
|
||||
'Ohne Status': 'bg-gray-300'
|
||||
};
|
||||
%>
|
||||
<div id="listCard" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-x-auto">
|
||||
<!-- Kopf der Karte: Gesamtzahl der Bewerbungen. Bewusst außerhalb des
|
||||
md:min-w-Rahmens unten, damit er auf dem Handy nicht mitscrollt. -->
|
||||
<div class="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 px-6 py-4">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white">Bewerbungen</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<span class="font-semibold text-gray-800 dark:text-gray-100"><%= statistics.total %></span>
|
||||
<%= statistics.total === 1 ? 'Bewerbung' : 'Bewerbungen' %> gesamt
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:min-w-[760px]">
|
||||
<!-- Column labels (desktop only — on phones the rows become stacked cards) -->
|
||||
<div class="app-head app-grid px-6 py-3 bg-gray-50 dark:bg-gray-700 text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||||
<span></span>
|
||||
<span>Datum</span>
|
||||
<span>Firma</span>
|
||||
<span>Stelle</span>
|
||||
<span>Art</span>
|
||||
<span>Status</span>
|
||||
<span>Aktionen</span>
|
||||
</div>
|
||||
<% if (applications.length > 0) { %>
|
||||
<%
|
||||
const groups = {};
|
||||
applications.forEach(a => {
|
||||
const key = (a.status && a.status.trim()) ? a.status.trim() : 'Ohne Status';
|
||||
@@ -315,6 +327,55 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Letzte Aktivitäten: die jüngsten Statusänderungen quer über alle
|
||||
Bewerbungen — der schnellste Blick darauf, was zuletzt passiert ist,
|
||||
ohne jede Gruppe aufklappen zu müssen. -->
|
||||
<section id="aktivitaetCard" class="bg-white dark:bg-gray-800 rounded-lg shadow-md mt-8">
|
||||
<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">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white">Letzte Aktivitäten</h2>
|
||||
<a href="/statistik#aktivitaet" class="text-sm text-blue-600 dark:text-blue-400 hover:underline">Alle in der Statistik</a>
|
||||
</div>
|
||||
|
||||
<% if (aktivitaet && aktivitaet.length) { %>
|
||||
<ul class="divide-y divide-gray-100 dark:divide-gray-700/60">
|
||||
<% aktivitaet.forEach(function (a) { %>
|
||||
<li>
|
||||
<% var ziel = a.bewerbung_id ? ('/bewerbung/' + a.bewerbung_id) : null; %>
|
||||
<<%= ziel ? 'a href="' + ziel + '"' : 'div' %> class="flex items-start gap-3 px-6 py-3 <%= ziel ? 'hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors' : '' %>">
|
||||
<span class="mt-1.5 inline-block w-2.5 h-2.5 rounded-full shrink-0 <%= statusDot[a.status] || 'bg-gray-300' %>"></span>
|
||||
<span class="min-w-0 flex-1">
|
||||
<span class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
||||
<% if (a.bewerbung_id) { %>
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white break-words"><%= a.firma || '(ohne Firma)' %></span>
|
||||
<% if (a.stelle) { %>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400 break-words">· <%= a.stelle %></span>
|
||||
<% } %>
|
||||
<% } else { %>
|
||||
<span class="text-sm font-medium text-gray-500 dark:text-gray-400">Gelöschte Bewerbung</span>
|
||||
<% } %>
|
||||
</span>
|
||||
<!-- Status, Datum und Kommentar in einer Zeile: ein rechtsbündiges
|
||||
Datum würde auf dem Handy in eine eigene Zeile umbrechen und
|
||||
dort ohne Bezug stehen. -->
|
||||
<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>
|
||||
<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>
|
||||
</<%= ziel ? 'a' : 'div' %>>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<% } else { %>
|
||||
<div class="px-6 py-10 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
Noch keine Statusänderungen protokolliert.
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function qsa(sel, ctx) {
|
||||
@@ -364,6 +425,7 @@
|
||||
var fehler = document.getElementById('searchError');
|
||||
var listCard = document.getElementById('listCard');
|
||||
var termineCard = document.getElementById('termineCard');
|
||||
var aktivitaetCard = document.getElementById('aktivitaetCard');
|
||||
|
||||
var MIN = 2; // kürzere Eingaben liefern nur Rauschen
|
||||
var MARK_START = '\u0002'; // Fundstellen-Marker aus lib/suche.js
|
||||
@@ -479,6 +541,7 @@
|
||||
panel.classList.add('hidden');
|
||||
if (listCard) listCard.classList.remove('hidden');
|
||||
if (termineCard) termineCard.classList.remove('hidden');
|
||||
if (aktivitaetCard) aktivitaetCard.classList.remove('hidden');
|
||||
input.setAttribute('aria-expanded', 'false');
|
||||
aktiv = -1;
|
||||
}
|
||||
@@ -498,6 +561,7 @@
|
||||
panel.classList.remove('hidden');
|
||||
if (listCard) listCard.classList.add('hidden');
|
||||
if (termineCard) termineCard.classList.add('hidden');
|
||||
if (aktivitaetCard) aktivitaetCard.classList.add('hidden');
|
||||
input.setAttribute('aria-expanded', 'true');
|
||||
aktiv = -1;
|
||||
}
|
||||
@@ -511,6 +575,7 @@
|
||||
panel.classList.remove('hidden');
|
||||
if (listCard) listCard.classList.add('hidden');
|
||||
if (termineCard) termineCard.classList.add('hidden');
|
||||
if (aktivitaetCard) aktivitaetCard.classList.add('hidden');
|
||||
}
|
||||
|
||||
async function suchen(q) {
|
||||
|
||||
+1
-1
@@ -307,7 +307,7 @@
|
||||
|
||||
<!-- Recent activity -->
|
||||
<div class="stat-card">
|
||||
<h2 class="text-gray-900 dark:text-white">Letzte Aktivität</h2>
|
||||
<h2 id="aktivitaet" class="text-gray-900 dark:text-white">Letzte Aktivität</h2>
|
||||
<p class="sub mb-5">Die jüngsten Statusänderungen deiner Bewerbungen.</p>
|
||||
<% if (aktivitaet.length) {
|
||||
const statusColor = (s) => ({
|
||||
|
||||
Reference in New Issue
Block a user