From 544eb4d745ce63a970a85e2037aad07c8f153898 Mon Sep 17 00:00:00 2001 From: Giuseppe Lombardo Date: Wed, 12 Aug 2026 19:11:21 +0000 Subject: [PATCH] Add Putzplan UI section --- web-dashboard/index.html | 168 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/web-dashboard/index.html b/web-dashboard/index.html index d0aa627..7911d5e 100644 --- a/web-dashboard/index.html +++ b/web-dashboard/index.html @@ -427,6 +427,37 @@

Offene Schadensmeldungen

+ + + +
+

Putzplan

+
+
@@ -444,6 +475,8 @@ SCHIMMEL: 'Schimmel', SCHLUESSEL_SCHLOSS: 'Schlüssel/Schloss', SONSTIGES: 'Sonstiges', }; const TICKET_STATUS_LABEL = { OPEN: 'Gemeldet', IN_PROGRESS: 'In Bearbeitung', RESOLVED: 'Erledigt', CLOSED: 'Geschlossen' }; + const CLEANING_AREA_LABEL = { BATHROOM_1: 'Bad 1', BATHROOM_2: 'Bad 2', KITCHEN: 'Wohnküche', HALLWAY_LAUNDRY: 'Waschraum/Flur' }; + const CLEANING_STATUS_LABEL = { PENDING: 'Offen', COMPLETED: 'Erledigt', VERIFIED: 'Bestätigt', MISSED: 'Verpasst' }; function formatEuro(n) { return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n || 0); @@ -609,11 +642,16 @@ const isLandlord = currentUser.role === 'LANDLORD' || currentUser.role === 'ADMIN'; document.getElementById('inviteSection').style.display = isLandlord ? 'block' : 'none'; document.getElementById('rentSectionTitle').textContent = isLandlord ? 'Miet-Ampel je Zimmer' : 'Meine Miete'; + document.getElementById('cleaningAssignSection').style.display = isLandlord ? 'block' : 'none'; + document.getElementById('cleaningSectionTitle').textContent = isLandlord ? 'Putzplan (alle Mieter)' : 'Mein Putzplan'; if (isLandlord) initInvitePanel(); + if (isLandlord) initCleaningAssignForm(); initTicketForm(); loadData(); + loadCleaningTasks(); setInterval(loadData, REFRESH_INTERVAL_MS); + setInterval(loadCleaningTasks, REFRESH_INTERVAL_MS); } // --------------------------------------------------------------------- @@ -955,6 +993,136 @@ } } + // --------------------------------------------------------------------- + // PUTZPLAN + // --------------------------------------------------------------------- + + async function initCleaningAssignForm() { + const userSelect = document.getElementById('cleaningUser'); + const areaSelect = document.getElementById('cleaningArea'); + userSelect.innerHTML = ''; + areaSelect.innerHTML = ''; + Object.keys(CLEANING_AREA_LABEL).forEach(key => { + const opt = document.createElement('option'); + opt.value = key; + opt.textContent = CLEANING_AREA_LABEL[key]; + areaSelect.appendChild(opt); + }); + + try { + const data = await apiFetch('/tenants'); + data.tenants.forEach(t => { + const opt = document.createElement('option'); + opt.value = t.id; + opt.textContent = t.room ? `${t.fullName} (${t.room.roomNumber})` : t.fullName; + userSelect.appendChild(opt); + }); + } catch (err) { + console.error('[cleaning-tasks] Mieterliste laden fehlgeschlagen:', err.message); + } + + const form = document.getElementById('cleaningForm'); + const errorBox = document.getElementById('cleaningFormError'); + form.onsubmit = async (e) => { + e.preventDefault(); + errorBox.style.display = 'none'; + const submitBtn = document.getElementById('cleaningSubmitBtn'); + submitBtn.disabled = true; + submitBtn.textContent = 'Weist zu…'; + try { + const assignedUserId = userSelect.value; + const area = areaSelect.value; + const weekOf = document.getElementById('cleaningWeekOf').value; + const dueDate = document.getElementById('cleaningDueDate').value; + await apiFetch('/cleaning-tasks', { + method: 'POST', + body: JSON.stringify({ assignedUserId, area, weekOf, dueDate }), + }); + form.reset(); + loadCleaningTasks(); + } catch (err) { + errorBox.textContent = err.message; + errorBox.style.display = 'block'; + } finally { + submitBtn.disabled = false; + submitBtn.textContent = 'Zuweisen'; + } + }; + } + + async function loadCleaningTasks() { + if (!currentUser) return; + try { + const data = await apiFetch('/cleaning-tasks?status=PENDING'); + renderCleaningTasks(data.tasks); + const dataCompleted = await apiFetch('/cleaning-tasks?status=COMPLETED'); + renderCleaningTasks(data.tasks.concat(dataCompleted.tasks)); + } catch (err) { + console.error('[cleaning-tasks] Laden fehlgeschlagen:', err.message); + } + } + + function renderCleaningTasks(tasks) { + const isLandlord = currentUser.role === 'LANDLORD' || currentUser.role === 'ADMIN'; + const list = document.getElementById('cleaningList'); + list.innerHTML = ''; + if (!tasks.length) { + list.appendChild(el('div', 'empty-state', isLandlord ? 'Aktuell keine Putzaufgaben.' : 'Aktuell keine offenen Putzaufgaben für dich. 🎉')); + return; + } + tasks.forEach(t => { + const row = el('div', 'ticket-row'); + const left = el('div'); + left.appendChild(el('div', 'title', CLEANING_AREA_LABEL[t.area] || t.area)); + left.appendChild(el('div', 'meta-text', + `${isLandlord && t.assignedUser ? t.assignedUser.fullName + ' · ' : ''}` + + `Fällig ${new Date(t.dueDate).toLocaleDateString('de-DE')} · ` + + CLEANING_STATUS_LABEL[t.status])); + row.appendChild(left); + + const right = el('div'); + right.style.display = 'flex'; + right.style.gap = '8px'; + right.style.alignItems = 'center'; + + if (!isLandlord && t.status === 'PENDING') { + const btn = document.createElement('button'); + btn.className = 'btn-secondary'; + btn.textContent = 'Erledigt'; + btn.onclick = () => completeCleaningTask(t.id); + right.appendChild(btn); + } + if (isLandlord && t.status === 'COMPLETED') { + const btn = document.createElement('button'); + btn.className = 'btn-secondary'; + btn.textContent = 'Bestätigen'; + btn.onclick = () => verifyCleaningTask(t.id); + right.appendChild(btn); + } + + row.appendChild(right); + list.appendChild(row); + }); + } + + async function completeCleaningTask(id) { + try { + await apiFetch(`/cleaning-tasks/${id}/complete`, { method: 'POST', body: JSON.stringify({}) }); + loadCleaningTasks(); + } catch (err) { + alert(err.message); + } + } + + async function verifyCleaningTask(id) { + try { + await apiFetch(`/cleaning-tasks/${id}`, { method: 'PATCH', body: JSON.stringify({ status: 'VERIFIED' }) }); + loadCleaningTasks(); + } catch (err) { + alert(err.message); + } + } + bootstrap();