Add Putzplan UI section

This commit is contained in:
Giuseppe Lombardo 2026-08-12 19:11:21 +00:00
parent 669aed188c
commit 544eb4d745

View File

@ -427,6 +427,37 @@
<h2>Offene Schadensmeldungen</h2> <h2>Offene Schadensmeldungen</h2>
<div class="ticket-list" id="ticketList"></div> <div class="ticket-list" id="ticketList"></div>
</section> </section>
<section id="cleaningAssignSection" style="display:none">
<h2>Putzaufgabe zuweisen</h2>
<div class="invite-card">
<div id="cleaningFormError" style="display:none" class="form-error"></div>
<form id="cleaningForm" class="invite-form-row" style="flex-wrap:wrap;">
<div class="field" style="flex:1 1 200px;">
<label for="cleaningUser">Mieter</label>
<select id="cleaningUser"></select>
</div>
<div class="field" style="flex:1 1 160px;">
<label for="cleaningArea">Bereich</label>
<select id="cleaningArea"></select>
</div>
<div class="field" style="flex:1 1 160px;">
<label for="cleaningWeekOf">Woche ab (Mo.)</label>
<input type="date" id="cleaningWeekOf" required />
</div>
<div class="field" style="flex:1 1 160px;">
<label for="cleaningDueDate">Fällig bis</label>
<input type="date" id="cleaningDueDate" required />
</div>
<button type="submit" class="btn-primary" style="width:auto" id="cleaningSubmitBtn">Zuweisen</button>
</form>
</div>
</section>
<section id="cleaningSection">
<h2 id="cleaningSectionTitle">Putzplan</h2>
<div class="ticket-list" id="cleaningList"></div>
</section>
</main> </main>
</div> </div>
@ -444,6 +475,8 @@
SCHIMMEL: 'Schimmel', SCHLUESSEL_SCHLOSS: 'Schlüssel/Schloss', SONSTIGES: 'Sonstiges', SCHIMMEL: 'Schimmel', SCHLUESSEL_SCHLOSS: 'Schlüssel/Schloss', SONSTIGES: 'Sonstiges',
}; };
const TICKET_STATUS_LABEL = { OPEN: 'Gemeldet', IN_PROGRESS: 'In Bearbeitung', RESOLVED: 'Erledigt', CLOSED: 'Geschlossen' }; 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) { function formatEuro(n) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n || 0); 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'; const isLandlord = currentUser.role === 'LANDLORD' || currentUser.role === 'ADMIN';
document.getElementById('inviteSection').style.display = isLandlord ? 'block' : 'none'; document.getElementById('inviteSection').style.display = isLandlord ? 'block' : 'none';
document.getElementById('rentSectionTitle').textContent = isLandlord ? 'Miet-Ampel je Zimmer' : 'Meine Miete'; 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) initInvitePanel();
if (isLandlord) initCleaningAssignForm();
initTicketForm(); initTicketForm();
loadData(); loadData();
loadCleaningTasks();
setInterval(loadData, REFRESH_INTERVAL_MS); 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(); bootstrap();
</script> </script>