// ----------------------------- // Projektübersicht — Personen-Ansicht (global, NUR LESEND) // ----------------------------- // Diese Datei rendert den Personen-Tab (Tab "Projektbeteiligte" in `App.html`) // als reine Anzeige. Seit der Ablösung von people/person_roles/person_absences // (server.js) werden Projektbeteiligte ausschließlich in der zentralen // Benutzerverwaltung (Bereich „Admin“) gepflegt; Anlegen/Bearbeiten/Löschen/ // Drag-and-Drop-Sortierung/Abwesenheits-Formulare gibt es hier bewusst nicht // mehr. EINE Ausnahme ist geblieben bzw. wieder dazugekommen: der // Teilnehmerkreis der aktuellen Sitzung (`currentWeek().people`). Er entsteht // nur beim Anlegen einer neuen Sitzung und wird beim Duplizieren // unverändert übernommen — ohne die Schaltflächen hier ("Zur Sitzung // hinzufügen"/"Aus Sitzung entfernen") wäre ein neu angelegter Benutzer in // keiner laufenden Sitzung zuordenbar, weder im Board noch in der // Auslastung noch in der Anwesenheit. Die Personendaten selbst kommen unverändert über `db.globalPeople` // (befüllt via `GET /load-all`, serverseitig jetzt aus `users`/`user_roles`/ // `user_absences` zusammengesetzt, firmenweit ohne Abteilungstrennung). Sie // wird von `App.html` nach `js/board/board.js` (vor `js/projects/projects.js`) // geladen und stellt die globale Funktion `renderPeople()` bereit, die u.a. // von `js/tabs-auth.js` (`showTab()`) und `js/init.js` aufgerufen wird. // renderPeople: baut die schreibgeschützte Liste (`.people-readonly`, eine // Bubble-Card mit den Personen als Listenzeilen) der Personen für den // Personen-Tab auf (Zeilen mit Kürzel, Vollname, E-Mail, Rollen, // aufklappbaren Abwesenheiten). Kein Formular, keine Lösch-/ // Bearbeiten-Aktionen, kein Drag-Handle. Zeigt zusätzlich einen Hinweis auf // die zentrale Benutzerverwaltung (Link nur für Admins sichtbar). Keine // Parameter, keine Rückgabe. function renderPeople(){ const people = db.globalPeople || []; const isAdmin = hatRecht('a.benutzer'); // Teilnehmerkreis pflegen: wie Benutzerverwaltung const w = currentWeek(); const inWeek = new Set((w && Array.isArray(w.people) ? w.people : []).map(p => p.id)); const locked = !w || isWeekLocked(w); let html = `
Projektbeteiligte werden zentral in der Benutzerverwaltung gepflegt.` + (isAdmin ? ` Zur Benutzerverwaltung` : '') + `
`; html += `
Wer in der aktuellen Sitzung` + (w ? ` ${esc(w.label)}` : '') + ` mitwirkt, erscheint als Spalte im Projektboard, in der Auslastung und in der Anwesenheit.` + (locked ? ` Die Sitzung ist gesperrt — der Kreis kann nicht geändert werden.` : '') + `
`; if(people.length === 0){ html += '
Keine Personen vorhanden.
'; document.getElementById('peopleList').innerHTML = html; return; } html += `
`; people.forEach(p => { const isOpen = personAbsenceOpen[p.id]; const roles = Array.isArray(p.role) ? p.role : []; const dabei = inWeek.has(p.id); // Die Schaltfläche liegt IM Kopfbereich, der das Aufklappen auslöst — // stopPropagation verhindert, dass ein Klick darauf zusätzlich die // Abwesenheiten auf- oder zuklappt. const weekAction = locked ? '' : (dabei ? `` : ``); html += `
`; html += `
${icon('chevronRight')} ${esc(p.name)} ${esc(p.fullName || '')} ${esc(p.email || '')} ${roles.length ? roles.map(r => esc(r)).join(', ') : 'Keine Rolle'} ${dabei ? 'in dieser Sitzung' : 'nicht in dieser Sitzung'} ${weekAction}
`; html += `
`; const absences = Array.isArray(p.absences) ? p.absences : []; if(absences.length > 0){ html += `
`; absences.forEach(a => { html += `
${esc(a.type || 'Abwesenheit')}: ${formatDateDE(parseDateISO(a.start))}–${formatDateDE(parseDateISO(a.end))} ${a.note ? `${esc(a.note)}` : ''}
`; }); html += `
`; } else { html += `
Keine Abwesenheiten hinterlegt.
`; } html += `
`; }); html += `
`; document.getElementById('peopleList').innerHTML = html; } // personAbsenceOpen: UI-Zustand, welche Personen-Karte ihre Abwesenheiten // aufgeklappt darstellt (Key = Personen-ID, Wert = boolean). Reine Anzeige- // Steuerung, wird nicht persistiert. let personAbsenceOpen = {}; // togglePersonAbsences: klappt die Abwesenheits-Liste einer Person auf bzw. zu // (Umschalten des Eintrags in `personAbsenceOpen`) und rendert neu. function togglePersonAbsences(personId){ personAbsenceOpen[personId] = !personAbsenceOpen[personId]; renderPeople(); } // addPersonToCurrentWeek: nimmt eine Person aus der globalen Liste in den // Teilnehmerkreis der aktuellen Sitzung auf. Nötig, weil der Kreis sonst nur // beim Anlegen einer neuen Sitzung entsteht: Ein später angelegter Benutzer // tauchte in laufenden (und in allen daraus duplizierten) Sitzungen nirgends // auf. Geschrieben wird über den normalen save()-Weg — `week.people` ist Teil // des Wochen-Datensatzes, den /save-week ohnehin schreibt. // Parameter: personId (String). Keine Rückgabe. function addPersonToCurrentWeek(personId){ if(guardWeekLocked()) return; const w = currentWeek(); if(!w) return; if(!Array.isArray(w.people)) w.people = []; if(w.people.some(p => p.id === personId)) return; const person = (db.globalPeople || []).find(p => p.id === personId); if(!person){ alert('Diese Person ist nicht mehr in der Benutzerverwaltung vorhanden.'); return; } // Tiefe Kopie: Der Teilnehmerkreis friert Name und Rollen zum Zeitpunkt der // Sitzung ein (genauso wie newWeek() es tut) und darf sich später nicht // rückwirkend mit der globalen Liste ändern. w.people.push(JSON.parse(JSON.stringify(person))); save(); renderAll(); } // removePersonFromCurrentWeek: nimmt eine Person wieder aus dem // Teilnehmerkreis der aktuellen Sitzung heraus. Verweigert wird das, solange // sie noch einem Projekt dieser Sitzung zugeordnet ist — sonst bliebe eine // Zuordnung ohne zugehörige Spalte zurück. Parameter: personId (String). function removePersonFromCurrentWeek(personId){ if(guardWeekLocked()) return; const w = currentWeek(); if(!w || !Array.isArray(w.people)) return; const person = w.people.find(p => p.id === personId); if(!person) return; const zugeordnet = (w.projects || []).filter(proj => { const assign = proj.assign || {}; return Object.keys(assign).some(key => key === personId || key.indexOf(personId + '|') === 0); }); if(zugeordnet.length > 0){ alert('"' + (person.fullName || person.name) + '" ist in dieser Sitzung noch ' + zugeordnet.length + ' Projekt(en) zugeordnet:\n\n' + zugeordnet.map(p => '· ' + (p.name || p.nr)).join('\n') + '\n\nBitte zuerst die Zuordnungen im Projektboard entfernen.'); return; } w.people = w.people.filter(p => p.id !== personId); if(w.attendance) delete w.attendance[personId]; save(); renderAll(); }