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
+
+
+ Putzaufgabe zuweisen
+
+
+
+
@@ -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();