diff --git a/backend/src/routes/storage.ts b/backend/src/routes/storage.ts index 18159f9..5d78b52 100644 --- a/backend/src/routes/storage.ts +++ b/backend/src/routes/storage.ts @@ -105,7 +105,13 @@ storageRouter.delete( // MÜLL-KALENDER // ---------------------------------------------------------------------------- -const TRASH_SELECT = { id: true, type: true, date: true }; +const TRASH_SELECT = { + id: true, + type: true, + date: true, + completedAt: true, + completedBy: { select: { id: true, fullName: true } }, +}; storageRouter.post( '/trash-schedule', @@ -146,6 +152,26 @@ storageRouter.get('/trash-schedule', requireAuth, async (req: AuthedRequest, res res.status(200).json({ entries }); }); +// Jeder Mieter (nicht nur der Vermieter) kann eine Mülltonne als +// "rausgestellt" markieren — das ist eine gemeinschaftliche Aufgabe, keine +// pro Person. Danach verschwindet die Erinnerung für alle. +storageRouter.post( + '/trash-schedule/:id/complete', + requireAuth, + async (req: AuthedRequest, res: Response) => { + try { + const entry = await prisma.trashSchedule.update({ + where: { id: req.params.id }, + data: { completedAt: new Date(), completedById: req.user!.id }, + select: TRASH_SELECT, + }); + res.status(200).json({ entry }); + } catch { + res.status(404).json({ error: 'Eintrag nicht gefunden' }); + } + }, +); + storageRouter.delete( '/trash-schedule/:id', requireAuth, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 31af41d..301dc02 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -189,6 +189,7 @@ model User { accessCodeAssigned AccessCode[] @relation("AccessCodeUser") accessCodesGiven AccessCode[] @relation("AccessCodeAssignedBy") announcementsMade Announcement[] + trashCompleted TrashSchedule[] notifications Notification[] invitationsSent Invitation[] @relation("InvitationsSent") storageSlots StorageSlot[] @@ -640,6 +641,13 @@ model TrashSchedule { date DateTime reminderSentAt DateTime? @map("reminder_sent_at") + // Wird gesetzt, sobald irgendein Mieter die Tonne rausgestellt hat — dann + // verschwindet die Erinnerung für ALLE Mieter (gemeinschaftliche Aufgabe, + // keine Erledigung pro Person). + completedAt DateTime? @map("completed_at") + completedById String? @map("completed_by_id") + completedBy User? @relation(fields: [completedById], references: [id]) + createdAt DateTime @default(now()) @map("created_at") @@unique([type, date]) diff --git a/web-dashboard/index.html b/web-dashboard/index.html index 0e4a005..4ab64f6 100644 --- a/web-dashboard/index.html +++ b/web-dashboard/index.html @@ -3283,8 +3283,14 @@ const from = today.toISOString().slice(0, 10); const to = weekAhead.toISOString().slice(0, 10); const data = await apiFetch(`/trash-schedule?from=${from}&to=${to}`); - data.entries.forEach(e => { - items.push({ date: new Date(e.date), icon: '🗑️', text: `Müll rausstellen: ${TRASH_TYPE_LABEL[e.type] || e.type} am ${new Date(e.date).toLocaleDateString('de-DE')}` }); + data.entries.filter(e => !e.completedAt).forEach(e => { + items.push({ + date: new Date(e.date), + icon: '🗑️', + text: `Müll rausstellen: ${TRASH_TYPE_LABEL[e.type] || e.type} am ${new Date(e.date).toLocaleDateString('de-DE')}`, + actionLabel: 'Erledigt', + onAction: () => completeTrashEntry(e.id), + }); }); } catch (err) { console.error('[notifications] Müllkalender laden fehlgeschlagen:', err.message); @@ -3316,10 +3322,26 @@ items.forEach(item => { const row = el('div', 'ticket-row'); row.appendChild(el('div', 'title', `${item.icon} ${item.text}`)); + if (item.onAction) { + const btn = document.createElement('button'); + btn.className = 'btn-secondary'; + btn.textContent = item.actionLabel; + btn.onclick = item.onAction; + row.appendChild(btn); + } list.appendChild(row); }); } + async function completeTrashEntry(id) { + try { + await apiFetch(`/trash-schedule/${id}/complete`, { method: 'POST' }); + loadNotifications(); + } catch (err) { + alert(err.message); + } + } + function buildProfileCompletenessBadge(user) { const missing = []; if (!user.phoneNumber) missing.push('Telefonnummer');