Benachrichtigungsglocke fuer ungelesene E-Mails
Neue Antworten werden meist automatisch einer Bewerbung zugeordnet und tauchten daher nie im Postfach auf - man bemerkte sie nicht. Eine Glocke im Header zeigt jetzt auf jeder Seite die Zahl ungelesener empfangener E-Mails (inkl. zugeordneter Antworten) und listet sie in einem Dropdown mit Absender, Betreff, Auszug und Link zur Bewerbung bzw. zum Postfach. - GET /api/notifications: Anzahl + neueste ungelesene Nachrichten. - POST /api/emails/mark-all-read: alle als gelesen markieren. - Postfach-Ansicht markiert unverknuepfte Mails als gelesen (leert die Glocke), zugeordnete Antworten werden beim Oeffnen der Bewerbung gelesen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1446,6 +1446,10 @@ initializeDatabase().then(() => {
|
||||
atts.forEach((a) => { (byEmail[a.email_id] = byEmail[a.email_id] || []).push(a); });
|
||||
emails.forEach((e) => { e.anhaenge = byEmail[e.id] || []; });
|
||||
decorateEmails(emails);
|
||||
// These are read now that they are shown — clears them from the bell.
|
||||
// (The `seen` values above are captured pre-update, so the "Neu" badge
|
||||
// still renders on this view.)
|
||||
await dbRun("UPDATE emails SET seen = 1 WHERE bewerbung_id IS NULL AND direction = 'in' AND seen = 0");
|
||||
}
|
||||
// Applications the user can assign an e-mail to (newest first).
|
||||
const bewerbungen = await dbAll(
|
||||
@@ -1502,7 +1506,7 @@ initializeDatabase().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Count of unlinked e-mails — drives the header badge on every page.
|
||||
// Count of unlinked e-mails — drives the "Postfach" header badge on every page.
|
||||
app.get('/api/emails/unassigned-count', async (req, res) => {
|
||||
try {
|
||||
const row = await dbGet('SELECT COUNT(*) as count FROM emails WHERE bewerbung_id IS NULL');
|
||||
@@ -1512,6 +1516,54 @@ initializeDatabase().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Unread received e-mails — drives the notification bell on every page. Returns
|
||||
// the total unread count plus the newest few as a ready-to-render list. Replies
|
||||
// auto-assigned to an application carry a link to that application; unassigned
|
||||
// mail links to the Postfach.
|
||||
app.get('/api/notifications', async (req, res) => {
|
||||
try {
|
||||
const cntRow = await dbGet(
|
||||
"SELECT COUNT(*) AS count FROM emails WHERE direction = 'in' AND seen = 0"
|
||||
);
|
||||
const rows = await dbAll(
|
||||
`SELECT e.id, e.from_addr, e.subject, e.body_text, e.body_html, e.email_date, e.bewerbung_id,
|
||||
b.firma, b.stelle
|
||||
FROM emails e LEFT JOIN bewerbungen b ON b.id = e.bewerbung_id
|
||||
WHERE e.direction = 'in' AND e.seen = 0
|
||||
ORDER BY datetime(e.email_date) DESC, e.id DESC
|
||||
LIMIT 30`
|
||||
);
|
||||
const items = rows.map((e) => {
|
||||
const fromName = String(e.from_addr || '').replace(/<[^>]*>/, '').replace(/"/g, '').trim()
|
||||
|| String(e.from_addr || '').trim();
|
||||
const snippet = emailPlainText(e).replace(/\s+/g, ' ').trim().slice(0, 140);
|
||||
return {
|
||||
id: e.id,
|
||||
from: fromName || '(unbekannt)',
|
||||
subject: e.subject || '(kein Betreff)',
|
||||
snippet,
|
||||
date: e.email_date || null,
|
||||
bewerbung_id: e.bewerbung_id || null,
|
||||
kontext: e.bewerbung_id ? [e.firma, e.stelle].filter(Boolean).join(' · ') : '',
|
||||
url: e.bewerbung_id ? ('/bewerbung/' + e.bewerbung_id + '#korrespondenz') : '/postfach',
|
||||
};
|
||||
});
|
||||
res.json({ count: cntRow ? cntRow.count : 0, items });
|
||||
} catch (error) {
|
||||
res.status(500).json({ count: 0, items: [] });
|
||||
}
|
||||
});
|
||||
|
||||
// Mark every received e-mail as read (clears the notification bell).
|
||||
app.post('/api/emails/mark-all-read', async (req, res) => {
|
||||
try {
|
||||
await dbRun("UPDATE emails SET seen = 1 WHERE direction = 'in' AND seen = 0");
|
||||
res.json({ ok: true });
|
||||
} catch (error) {
|
||||
res.status(500).json({ ok: false });
|
||||
}
|
||||
});
|
||||
|
||||
// Add a timeline entry (status change with date + comment)
|
||||
app.post('/bewerbung/:id/verlauf', async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user