Add Putzplan UI section
This commit is contained in:
parent
669aed188c
commit
544eb4d745
@ -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>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user