Sidebar: Mieter zu Mieterverwaltung mit Untermenüs Mietereinladungen/Mieterdaten umbauen

This commit is contained in:
Giuseppe Lombardo 2026-08-13 18:03:09 +00:00
parent 33881db9e1
commit 23d58e48d9
2 changed files with 65 additions and 2 deletions

View File

@ -131,6 +131,7 @@ const CONTRACT_DOCUMENTS_SELECT = {
select: {
id: true,
fullName: true,
email: true,
phoneNumber: true,
firstResidenceAddress: true,
idDocumentFrontUrl: true,

View File

@ -450,6 +450,17 @@
.nav-item .nav-icon { font-size: 15px; width: 18px; text-align: center; }
.nav-item:hover { background: var(--bg); }
.nav-item.active { background: var(--green-bg); color: var(--green); font-weight: 600; }
.nav-group-title {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 12px 2px;
font-size: 13.5px;
color: var(--text);
font-weight: 600;
}
.nav-subitem { padding-left: 34px; font-size: 13px; }
.content-area { flex: 1; min-width: 0; }
.page { display: none; }
@ -578,7 +589,9 @@
<div class="sidebar-brand">WG Nackenheim<span>Vermieter-Cockpit</span></div>
<nav id="sidebarNav">
<button class="nav-item active" data-page="page-overview" onclick="switchPage('page-overview')"><span class="nav-icon">🏠</span>Übersicht</button>
<button class="nav-item" data-page="page-tenants" onclick="switchPage('page-tenants')"><span class="nav-icon">👥</span>Mieter</button>
<div class="nav-group-title"><span class="nav-icon">👥</span>Mieterverwaltung</div>
<button class="nav-item nav-subitem" data-page="page-tenant-invites" onclick="switchPage('page-tenant-invites')">Mietereinladungen</button>
<button class="nav-item nav-subitem" data-page="page-tenant-data" onclick="switchPage('page-tenant-data')">Mieterdaten</button>
<button class="nav-item" data-page="page-tickets" onclick="switchPage('page-tickets')"><span class="nav-icon">🛠️</span>Tickets &amp; Handwerker</button>
<button class="nav-item" data-page="page-cleaning" onclick="switchPage('page-cleaning')"><span class="nav-icon">🧹</span>Putzplan</button>
<button class="nav-item" data-page="page-finance" onclick="switchPage('page-finance')"><span class="nav-icon">💶</span>WG-Kasse</button>
@ -649,7 +662,7 @@
</section>
</div>
<div class="page" id="page-tenants">
<div class="page" id="page-tenant-invites">
<section id="inviteSection" style="display:none">
<h2>Mieter einladen</h2>
@ -703,6 +716,15 @@
</div>
</section>
</div>
<div class="page" id="page-tenant-data">
<section id="tenantDataSection" style="display:none">
<h2>Mieterdaten</h2>
<div class="ticket-list" id="tenantDataList"></div>
</section>
<section id="ratingsSection">
<h2>Mieter-Bewertung</h2>
<div class="invite-card" style="margin-bottom:16px;">
@ -1449,6 +1471,7 @@
const isLandlord = currentUser.role === 'LANDLORD' || currentUser.role === 'ADMIN';
document.getElementById('inviteSection').style.display = isLandlord ? 'block' : 'none';
document.getElementById('tenantDataSection').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';
@ -1495,6 +1518,7 @@
loadContractDocuments();
loadContractArchive();
loadAnnouncements();
if (isLandlord) loadTenantData();
setInterval(loadData, REFRESH_INTERVAL_MS);
setInterval(loadCleaningTasks, REFRESH_INTERVAL_MS);
@ -1512,6 +1536,7 @@
setInterval(loadContractDocuments, REFRESH_INTERVAL_MS);
setInterval(loadContractArchive, REFRESH_INTERVAL_MS);
setInterval(loadAnnouncements, REFRESH_INTERVAL_MS);
if (isLandlord) setInterval(loadTenantData, REFRESH_INTERVAL_MS);
}
// ---------------------------------------------------------------------
@ -3576,6 +3601,43 @@
return wrap;
}
// Mieterdaten-Übersicht (nur Vermieter/Admin): eine Zeile je aktivem
// Vertrag mit Kontaktdaten + derselben Profil-Vollständigkeits-/Schufa-
// Anzeige wie bei den Vertragsdokumenten — nutzt bewusst denselben
// Endpunkt (/contracts/documents), statt die Daten zu duplizieren.
async function loadTenantData() {
try {
const data = await apiFetch('/contracts/documents');
renderTenantData(data.contracts || []);
} catch (err) {
console.error('[tenantData] Laden fehlgeschlagen:', err.message);
}
}
function renderTenantData(contracts) {
const list = document.getElementById('tenantDataList');
list.innerHTML = '';
if (!contracts.length) {
list.appendChild(el('div', 'empty-state', 'Keine aktiven Mieter gefunden.'));
return;
}
contracts.forEach(c => {
const card = el('div', 'ticket-row');
card.style.flexDirection = 'column';
card.style.alignItems = 'stretch';
card.style.gap = '10px';
const top = el('div');
top.appendChild(el('div', 'title', `${c.user.fullName} · ${c.room.roomNumber}`));
top.appendChild(el('div', 'meta-text',
`${c.user.email}${c.user.phoneNumber ? ' · ' + c.user.phoneNumber : ''}`));
card.appendChild(top);
card.appendChild(buildProfileCompletenessBadge(c.user));
list.appendChild(card);
});
}
function renderContractDocuments(contracts) {
const isLandlord = currentUser.role === 'LANDLORD' || currentUser.role === 'ADMIN';
const list = document.getElementById('contractDocumentsList');