All new
alle new
This commit is contained in:
1 parent
da64472251
commit
5b20adee48
1605 files changed
+396517
-1674
No files matched your search
@@ -0,0 +1,480 @@
|
||||
// -----------------------------
|
||||
// Benutzerverwaltung (Abschnitt „Benutzerverwaltung" des Bereichs #admin)
|
||||
// -----------------------------
|
||||
// Laedt und rendert die Benutzerliste ueber die admin-only Endpunkte aus
|
||||
// server.js (GET/POST/PATCH/DELETE /admin/users, POST
|
||||
// /admin/users/:id/reset-password) und stellt die Dialoge bereit: neuen
|
||||
// Benutzer anlegen, bestehenden bearbeiten (inkl. Wochenstunden und
|
||||
// Abwesenheiten), Passwort zuruecksetzen, aktivieren/deaktivieren und
|
||||
// endgueltig loeschen.
|
||||
//
|
||||
// Das war bis zum Umbau eine EIGENE SEITE (public/admin-users.html) mit
|
||||
// eigenem Seiteneinstieg, eigener Anmeldepruefung und einer nachgebauten,
|
||||
// abgespeckten Kopfzeile statt der Seitenleiste. Jetzt ist es ein Abschnitt
|
||||
// des Bereichs „Admin" wie jeder andere: renderAdminUsers() wird von
|
||||
// showAdminSection() gerufen, sobald der Abschnitt sichtbar wird, und die
|
||||
// Seitenleiste bleibt stehen. Eine eigene Anmeldepruefung braucht es hier
|
||||
// nicht mehr — der Bereich ist ohnehin nur fuer Administratoren erreichbar
|
||||
// (showTab() in js/tabs-auth.js), und die eigentliche Sicherheitsgrenze sind
|
||||
// die requireRole('admin')-Endpunkte auf dem Server.
|
||||
|
||||
(function(){
|
||||
// usersCache: zuletzt vom Server geladene Benutzerliste (für die Modale,
|
||||
// damit sie nicht erneut nachgeladen werden muss).
|
||||
let usersCache = [];
|
||||
// editingUserId: id des im Bearbeiten-Modal geöffneten Benutzers.
|
||||
let editingUserId = null;
|
||||
// personIdManuallyEdited: verhindert, dass eine manuelle Eingabe im
|
||||
// Person-ID-Feld des Neu-Anlegen-Modals von der Auto-Vorschau überschrieben wird.
|
||||
let personIdManuallyEdited = false;
|
||||
// fmtCapacity: Wochenstunden in deutscher Schreibweise, ohne überflüssige
|
||||
// Nachkommastelle (40 statt 40,0 — aber 32,5 bleibt 32,5).
|
||||
// Parameter: h (Zahl). Rückgabe: String.
|
||||
function fmtCapacity(h){
|
||||
const n = Number(h) || 0;
|
||||
return (Number.isInteger(n) ? String(n) : n.toFixed(1).replace('.', ',')) + ' h';
|
||||
}
|
||||
|
||||
// initialsManuallyEdited: gleiches Muster für das Kürzel-Feld.
|
||||
let initialsManuallyEdited = false;
|
||||
// editUserAbsencesCache: zuletzt geladene Abwesenheiten des im
|
||||
// Bearbeiten-Modal geöffneten Benutzers (für Rendern/Löschen ohne erneuten
|
||||
// Request pro Aktion).
|
||||
let editUserAbsencesCache = [];
|
||||
|
||||
// renderDepartmentOptions: befüllt ein <select> mit einer <option> pro
|
||||
// Eintrag in DEPARTMENTS (js/globals.js) — bewusst ohne Leer-Option, die
|
||||
// Abteilung ist ein Pflichtfeld.
|
||||
function renderDepartmentOptions(selectId){
|
||||
const sel = document.getElementById(selectId);
|
||||
if(!sel) return;
|
||||
sel.innerHTML = Object.keys(DEPARTMENTS).map(key =>
|
||||
`<option value="${esc(key)}">${esc(DEPARTMENTS[key].label)}</option>`).join('');
|
||||
}
|
||||
|
||||
// renderRoleCheckboxes: befüllt einen Container mit einer Checkbox pro
|
||||
// Eintrag in ROLES (js/globals.js) — die fachlichen Rollen
|
||||
// (Planung/Projektleitung/Bauüberwachung), nicht zu verwechseln mit der
|
||||
// Systemrolle (Mitglied/Administrator) im "Rolle"-Feld.
|
||||
function renderRoleCheckboxes(containerId){
|
||||
const el = document.getElementById(containerId);
|
||||
if(!el) return;
|
||||
el.innerHTML = ROLES.map(r =>
|
||||
`<label><input type="checkbox" value="${esc(r)}"> ${esc(r)}</label>`).join('');
|
||||
}
|
||||
|
||||
// checkedRoleValues: liest die angehakten Fachrollen-Checkboxen eines
|
||||
// Containers als String-Array aus.
|
||||
function checkedRoleValues(containerId){
|
||||
return Array.from(document.querySelectorAll('#' + containerId + ' input[type="checkbox"]:checked')).map(cb => cb.value);
|
||||
}
|
||||
|
||||
// formulareVorbereitet: Abteilungs-Auswahl und Rollen-Ankreuzfelder muessen
|
||||
// nur einmal aufgebaut werden; renderAdminUsers() laeuft aber bei jedem
|
||||
// Wechsel in den Abschnitt.
|
||||
let formulareVorbereitet = false;
|
||||
|
||||
// renderAdminUsers: Einstiegspunkt des Abschnitts, von showAdminSection()
|
||||
// gerufen. Baut beim ersten Mal die Auswahlfelder der Dialoge auf und laedt
|
||||
// danach die Benutzerliste. Eine Rollenpruefung findet hier nicht mehr
|
||||
// statt — der Bereich „Admin" ist fuer Nicht-Administratoren gar nicht
|
||||
// erreichbar (showTab() leitet sie um) und die Endpunkte sind serverseitig
|
||||
// abgesichert. Keine Parameter, keine Rueckgabe.
|
||||
window.renderAdminUsers = function(){
|
||||
if(!formulareVorbereitet){
|
||||
renderDepartmentOptions('newUserDepartment');
|
||||
renderDepartmentOptions('editUserDepartment');
|
||||
renderRoleCheckboxes('newUserRoleCheckboxes');
|
||||
renderRoleCheckboxes('editUserRoleCheckboxes');
|
||||
formulareVorbereitet = true;
|
||||
}
|
||||
loadAndRenderUsers();
|
||||
};
|
||||
|
||||
// apiFetch: kleiner fetch-Wrapper für die /admin/users*-Endpunkte. Liefert
|
||||
// immer das geparste JSON zurück (auch bei Fehlerstatus), damit die
|
||||
// Aufrufer serverseitige Fehlermeldungen ({ok:false, error}) anzeigen können.
|
||||
function apiFetch(path, options){
|
||||
return fetch(baseUrl() + path, Object.assign({
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
||||
}, options || {})).then(r => r.json().then(data => ({ status: r.status, ok: r.ok, data })));
|
||||
}
|
||||
|
||||
// loadAndRenderUsers: lädt die Benutzerliste und rendert die Tabelle.
|
||||
function loadAndRenderUsers(){
|
||||
apiFetch('/admin/users').then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
document.getElementById('usersTableBody').innerHTML =
|
||||
'<tr><td colspan="9">Fehler beim Laden: ' + esc((res.data && res.data.error) || ('HTTP ' + res.status)) + '</td></tr>';
|
||||
return;
|
||||
}
|
||||
usersCache = res.data.users || [];
|
||||
renderUsersTable();
|
||||
}).catch(err => {
|
||||
document.getElementById('usersTableBody').innerHTML =
|
||||
'<tr><td colspan="9">Verbindung zum Server fehlgeschlagen: ' + esc(err.message) + '</td></tr>';
|
||||
});
|
||||
}
|
||||
|
||||
// renderUsersTable: baut die Tabellenzeilen aus usersCache auf.
|
||||
function renderUsersTable(){
|
||||
const body = document.getElementById('usersTableBody');
|
||||
if(usersCache.length === 0){
|
||||
body.innerHTML = '<tr><td colspan="9">Keine Benutzer vorhanden.</td></tr>';
|
||||
return;
|
||||
}
|
||||
body.innerHTML = usersCache.map(u => {
|
||||
const statusParts = [];
|
||||
statusParts.push(u.active ? '<span class="badge-active">Aktiv</span>' : '<span class="badge-inactive">Deaktiviert</span>');
|
||||
if(u.mustChangePassword) statusParts.push('<span class="badge-warn">Passwortwechsel erforderlich</span>');
|
||||
const toggleLabel = u.active ? 'Deaktivieren' : 'Aktivieren';
|
||||
const isSelf = currentUser && currentUser.id === u.id;
|
||||
const deleteBtn = (!u.active)
|
||||
? `<button class="danger small" ${isSelf ? 'disabled' : ''} onclick="hardDeleteUser(${u.id}, '${escJs(u.username)}')">Endgültig löschen</button>`
|
||||
: '';
|
||||
const deptLabel = (DEPARTMENTS[u.department] && DEPARTMENTS[u.department].label) || esc(u.department || '');
|
||||
return `<tr>
|
||||
<td>${esc(u.username)}</td>
|
||||
<td>${esc(u.displayName)}</td>
|
||||
<td>${esc(u.email || '')}</td>
|
||||
<td>${u.role === 'admin' ? 'Administrator' : 'Mitglied'}</td>
|
||||
<td>${deptLabel}</td>
|
||||
<td>${esc(u.initials || '')}</td>
|
||||
<td>${u.capacityHours ? esc(fmtCapacity(u.capacityHours)) : '<span class="hint" style="margin:0">voll</span>'}</td>
|
||||
<td>${esc(u.personId || '')}</td>
|
||||
<td>${statusParts.join('<br>')}</td>
|
||||
<td class="actions-cell">
|
||||
<button class="secondary small" onclick="openEditUserModal(${u.id})">Bearbeiten</button>
|
||||
<button class="secondary small" onclick="resetUserPassword(${u.id}, '${escJs(u.username)}')">Passwort zurücksetzen</button>
|
||||
<button class="secondary small" ${isSelf ? 'disabled title="Sie können sich nicht selbst deaktivieren."' : ''} onclick="toggleUserActive(${u.id}, ${u.active})">${toggleLabel}</button>
|
||||
${deleteBtn}
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// escJs: escaped einen String für die Einbettung in ein einfach gequotetes
|
||||
// JS-Literal innerhalb eines onclick-Attributs. Das doppelte
|
||||
// Anführungszeichen MUSS mit maskiert werden: Der Handler steht in einem
|
||||
// doppelt gequoteten HTML-Attribut, und ein Benutzername wie
|
||||
// `test" onmouseover="…` brach es sonst auf. Dasselbe gilt für spitze
|
||||
// Klammern, die das Attribut vorzeitig beenden könnten.
|
||||
function escJs(s){
|
||||
return String(s || '')
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// showGeneratedPassword: zeigt ein neu generiertes Passwort einmalig und gut
|
||||
// sichtbar in einer Box oben auf der Seite an (zusätzlich zum alert(), da es
|
||||
// sonst nirgends gespeichert/angezeigt wird).
|
||||
function showGeneratedPassword(username, password){
|
||||
const box = document.getElementById('generatedPasswordBox');
|
||||
box.innerHTML = 'Neues Passwort für <b>' + esc(username) + '</b>: <b>' + esc(password) + '</b> ' +
|
||||
'(bitte jetzt notieren/weitergeben — wird nicht erneut angezeigt).';
|
||||
box.style.display = '';
|
||||
alert('Neues Passwort für ' + username + ':\n\n' + password + '\n\nBitte jetzt notieren, es wird nicht erneut angezeigt.');
|
||||
}
|
||||
|
||||
// ---------- Neuer-Benutzer-Modal ----------
|
||||
|
||||
window.openNewUserModal = function(){
|
||||
document.getElementById('newUserUsername').value = '';
|
||||
document.getElementById('newUserDisplayName').value = '';
|
||||
document.getElementById('newUserEmail').value = '';
|
||||
document.getElementById('newUserRole').value = 'member';
|
||||
document.getElementById('newUserDepartment').value = DEFAULT_DEPARTMENT;
|
||||
document.getElementById('newUserInitials').value = '';
|
||||
document.getElementById('newUserPersonId').value = '';
|
||||
document.querySelectorAll('#newUserRoleCheckboxes input[type="checkbox"]').forEach(cb => { cb.checked = false; });
|
||||
document.getElementById('newUserPassword').value = '';
|
||||
document.getElementById('newUserPassword').style.display = 'none';
|
||||
document.querySelector('input[name="newUserPwMode"][value="generate"]').checked = true;
|
||||
document.getElementById('newUserError').textContent = '';
|
||||
personIdManuallyEdited = false;
|
||||
initialsManuallyEdited = false;
|
||||
document.getElementById('newUserModal').style.display = 'flex';
|
||||
document.getElementById('newUserUsername').focus();
|
||||
};
|
||||
|
||||
window.closeNewUserModal = function(){
|
||||
document.getElementById('newUserModal').style.display = 'none';
|
||||
};
|
||||
|
||||
// onNewUserDisplayNameInput: schlägt beim Tippen des Anzeigenamens eine
|
||||
// Person-ID über personIdForName() vor (js/utils.js) — nur solange das Feld
|
||||
// nicht bereits manuell bearbeitet wurde (Kollisionsvermeidung laut Plan).
|
||||
window.onNewUserDisplayNameInput = function(){
|
||||
const name = document.getElementById('newUserDisplayName').value;
|
||||
if(!personIdManuallyEdited){
|
||||
document.getElementById('newUserPersonId').value = name.trim() ? personIdForName(name) : '';
|
||||
}
|
||||
if(!initialsManuallyEdited){
|
||||
document.getElementById('newUserInitials').value = name.trim() ? initialsForName(name) : '';
|
||||
}
|
||||
};
|
||||
|
||||
window.onNewUserPwModeChange = function(){
|
||||
const manual = document.querySelector('input[name="newUserPwMode"]:checked').value === 'manual';
|
||||
document.getElementById('newUserPassword').style.display = manual ? '' : 'none';
|
||||
};
|
||||
|
||||
window.saveNewUser = function(){
|
||||
const username = document.getElementById('newUserUsername').value.trim();
|
||||
const displayName = document.getElementById('newUserDisplayName').value.trim();
|
||||
const email = document.getElementById('newUserEmail').value.trim();
|
||||
const role = document.getElementById('newUserRole').value;
|
||||
const department = document.getElementById('newUserDepartment').value;
|
||||
const initials = document.getElementById('newUserInitials').value.trim();
|
||||
const capacityHours = document.getElementById('newUserCapacity').value.trim();
|
||||
const personId = document.getElementById('newUserPersonId').value.trim();
|
||||
const roles = checkedRoleValues('newUserRoleCheckboxes');
|
||||
const pwMode = document.querySelector('input[name="newUserPwMode"]:checked').value;
|
||||
const errEl = document.getElementById('newUserError');
|
||||
errEl.textContent = '';
|
||||
|
||||
if(!username || !displayName){
|
||||
errEl.textContent = 'Benutzername und Anzeigename sind erforderlich.';
|
||||
return;
|
||||
}
|
||||
if(!department){
|
||||
errEl.textContent = 'Abteilung ist erforderlich.';
|
||||
return;
|
||||
}
|
||||
// Ohne Fachrolle ist der Benutzer in der ganzen Anwendung unsichtbar:
|
||||
// peopleByRole() (js/utils.js) gruppiert ausschließlich über die Rollen —
|
||||
// das Konto erschiene weder als Board-Spalte noch in der Auslastung noch
|
||||
// im PDF-Export, ohne dass es dafür irgendeinen Hinweis gäbe.
|
||||
if(!roles || roles.length === 0){
|
||||
errEl.textContent = 'Bitte mindestens eine Rolle wählen (Planung, Projektleitung oder Bauüberwachung) — sonst erscheint der Benutzer in keinem Projektboard.';
|
||||
return;
|
||||
}
|
||||
// Ohne Personen-Kennung lässt sich das Konto keiner Person in den
|
||||
// Sitzungen zuordnen (Aufgaben im Dashboard, Zuordnungen im Board).
|
||||
if(!personId){
|
||||
errEl.textContent = 'Bitte eine Personen-Kennung vergeben — ohne sie kann der Benutzer keiner Sitzung und keinen Aufgaben zugeordnet werden.';
|
||||
return;
|
||||
}
|
||||
const body = { username, displayName, email, role, personId, department, initials, capacityHours, roles };
|
||||
if(pwMode === 'manual'){
|
||||
const password = document.getElementById('newUserPassword').value;
|
||||
if(password.length < 8){
|
||||
errEl.textContent = 'Manuelles Passwort muss mindestens 8 Zeichen haben.';
|
||||
return;
|
||||
}
|
||||
body.password = password;
|
||||
} else {
|
||||
body.generatePassword = true;
|
||||
}
|
||||
|
||||
apiFetch('/admin/users', { method: 'POST', body: JSON.stringify(body) }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
errEl.textContent = (res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')');
|
||||
return;
|
||||
}
|
||||
closeNewUserModal();
|
||||
loadAndRenderUsers();
|
||||
if(res.data.generatedPassword){
|
||||
showGeneratedPassword(username, res.data.generatedPassword);
|
||||
}
|
||||
}).catch(err => { errEl.textContent = 'Verbindung zum Server fehlgeschlagen: ' + err.message; });
|
||||
};
|
||||
|
||||
// ---------- Benutzer-bearbeiten-Modal ----------
|
||||
|
||||
window.openEditUserModal = function(id){
|
||||
const u = usersCache.find(x => x.id === id);
|
||||
if(!u) return;
|
||||
editingUserId = id;
|
||||
document.getElementById('editUserUsernameLabel').textContent = 'Benutzername: ' + u.username + ' (nicht änderbar)';
|
||||
document.getElementById('editUserDisplayName').value = u.displayName || '';
|
||||
document.getElementById('editUserEmail').value = u.email || '';
|
||||
document.getElementById('editUserRole').value = u.role;
|
||||
document.getElementById('editUserDepartment').value = u.department || DEFAULT_DEPARTMENT;
|
||||
document.getElementById('editUserInitials').value = u.initials || '';
|
||||
document.getElementById('editUserCapacity').value = u.capacityHours ? u.capacityHours : '';
|
||||
document.getElementById('editUserPersonId').value = u.personId || '';
|
||||
const userRoles = u.roles || [];
|
||||
document.querySelectorAll('#editUserRoleCheckboxes input[type="checkbox"]').forEach(cb => {
|
||||
cb.checked = userRoles.includes(cb.value);
|
||||
});
|
||||
document.getElementById('editUserError').textContent = '';
|
||||
document.getElementById('editUserAbsenceError').textContent = '';
|
||||
document.getElementById('editUserAbsenceStart').value = '';
|
||||
document.getElementById('editUserAbsenceEnd').value = '';
|
||||
document.getElementById('editUserAbsenceNote').value = '';
|
||||
editUserAbsencesCache = [];
|
||||
renderEditUserAbsences();
|
||||
loadEditUserAbsences(id);
|
||||
document.getElementById('editUserModal').style.display = 'flex';
|
||||
};
|
||||
|
||||
window.closeEditUserModal = function(){
|
||||
document.getElementById('editUserModal').style.display = 'none';
|
||||
editingUserId = null;
|
||||
editUserAbsencesCache = [];
|
||||
};
|
||||
|
||||
window.saveEditUser = function(){
|
||||
if(editingUserId === null) return;
|
||||
const errEl = document.getElementById('editUserError');
|
||||
errEl.textContent = '';
|
||||
const department = document.getElementById('editUserDepartment').value;
|
||||
if(!department){
|
||||
errEl.textContent = 'Abteilung ist erforderlich.';
|
||||
return;
|
||||
}
|
||||
const body = {
|
||||
displayName: document.getElementById('editUserDisplayName').value.trim(),
|
||||
email: document.getElementById('editUserEmail').value.trim(),
|
||||
role: document.getElementById('editUserRole').value,
|
||||
department,
|
||||
initials: document.getElementById('editUserInitials').value.trim(),
|
||||
capacityHours: document.getElementById('editUserCapacity').value.trim(),
|
||||
personId: document.getElementById('editUserPersonId').value.trim(),
|
||||
roles: checkedRoleValues('editUserRoleCheckboxes'),
|
||||
};
|
||||
apiFetch('/admin/users/' + editingUserId, { method: 'PATCH', body: JSON.stringify(body) }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
errEl.textContent = (res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')');
|
||||
return;
|
||||
}
|
||||
closeEditUserModal();
|
||||
loadAndRenderUsers();
|
||||
}).catch(err => { errEl.textContent = 'Verbindung zum Server fehlgeschlagen: ' + err.message; });
|
||||
};
|
||||
|
||||
// ---------- Abwesenheiten (Bearbeiten-Modal) ----------
|
||||
// GET /admin/users liefert bewusst keine Abwesenheiten mit (server.js), daher
|
||||
// werden sie hier separat über GET /admin/users/:id/absences nachgeladen und
|
||||
// Änderungen über die bereits bestehenden requireAuth-Endpunkte
|
||||
// POST/DELETE /users/:id/absences vorgenommen.
|
||||
|
||||
// loadEditUserAbsences: lädt die Abwesenheiten des gerade bearbeiteten
|
||||
// Benutzers und rendert die Liste im Bearbeiten-Modal.
|
||||
function loadEditUserAbsences(id){
|
||||
apiFetch('/admin/users/' + id + '/absences').then(res => {
|
||||
if(editingUserId !== id) return; // Modal wurde inzwischen gewechselt/geschlossen
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
document.getElementById('editUserAbsenceError').textContent =
|
||||
(res.data && res.data.error) || ('Fehler beim Laden (HTTP ' + res.status + ')');
|
||||
return;
|
||||
}
|
||||
editUserAbsencesCache = res.data.absences || [];
|
||||
renderEditUserAbsences();
|
||||
}).catch(err => {
|
||||
document.getElementById('editUserAbsenceError').textContent = 'Verbindung zum Server fehlgeschlagen: ' + err.message;
|
||||
});
|
||||
}
|
||||
|
||||
// renderEditUserAbsences: baut die Chip-Liste der Abwesenheiten aus
|
||||
// editUserAbsencesCache auf (gleiche Markup-/CSS-Struktur wie die
|
||||
// Abwesenheitsliste im Projektbeteiligte-Tab: .absence-list/.absence-chip).
|
||||
function renderEditUserAbsences(){
|
||||
const el = document.getElementById('editUserAbsenceList');
|
||||
if(editUserAbsencesCache.length === 0){
|
||||
el.innerHTML = '<span class="hint">Keine Abwesenheiten erfasst.</span>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = editUserAbsencesCache.map(a => `<div class="absence-chip">
|
||||
<span>${esc(a.type || 'Abwesenheit')}: ${formatDateDE(parseDateISO(a.start))}–${formatDateDE(parseDateISO(a.end))}</span>
|
||||
${a.note ? `<small>${esc(a.note)}</small>` : ''}
|
||||
<button class="danger small" onclick="deleteEditUserAbsence('${escJs(a.id)}')">×</button>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// addEditUserAbsence: legt über POST /users/:id/absences eine neue
|
||||
// Abwesenheit für den gerade bearbeiteten Benutzer an und lädt die Liste neu.
|
||||
window.addEditUserAbsence = function(){
|
||||
if(editingUserId === null) return;
|
||||
const errEl = document.getElementById('editUserAbsenceError');
|
||||
errEl.textContent = '';
|
||||
const start = document.getElementById('editUserAbsenceStart').value;
|
||||
const end = document.getElementById('editUserAbsenceEnd').value;
|
||||
const type = document.getElementById('editUserAbsenceType').value;
|
||||
const note = document.getElementById('editUserAbsenceNote').value.trim();
|
||||
if(!start || !end){
|
||||
errEl.textContent = 'Start- und Enddatum erforderlich.';
|
||||
return;
|
||||
}
|
||||
apiFetch('/users/' + editingUserId + '/absences', { method: 'POST', body: JSON.stringify({ start, end, type, note }) }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
errEl.textContent = (res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')');
|
||||
return;
|
||||
}
|
||||
document.getElementById('editUserAbsenceStart').value = '';
|
||||
document.getElementById('editUserAbsenceEnd').value = '';
|
||||
document.getElementById('editUserAbsenceNote').value = '';
|
||||
loadEditUserAbsences(editingUserId);
|
||||
}).catch(err => { errEl.textContent = 'Verbindung zum Server fehlgeschlagen: ' + err.message; });
|
||||
};
|
||||
|
||||
// deleteEditUserAbsence: löscht eine Abwesenheit über DELETE
|
||||
// /users/:id/absences/:absenceId und lädt die Liste neu.
|
||||
window.deleteEditUserAbsence = function(absenceId){
|
||||
if(editingUserId === null) return;
|
||||
const errEl = document.getElementById('editUserAbsenceError');
|
||||
errEl.textContent = '';
|
||||
apiFetch('/users/' + editingUserId + '/absences/' + absenceId, { method: 'DELETE' }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
errEl.textContent = (res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')');
|
||||
return;
|
||||
}
|
||||
loadEditUserAbsences(editingUserId);
|
||||
}).catch(err => { errEl.textContent = 'Verbindung zum Server fehlgeschlagen: ' + err.message; });
|
||||
};
|
||||
|
||||
// ---------- Zeilen-Aktionen (Passwort-Reset, Aktivieren/Deaktivieren, Löschen) ----------
|
||||
|
||||
window.resetUserPassword = function(id, username){
|
||||
if(!confirm('Passwort für "' + username + '" wirklich zurücksetzen? Bisherige Sitzungen dieses Benutzers werden beendet.')) return;
|
||||
apiFetch('/admin/users/' + id + '/reset-password', { method: 'POST' }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
alert((res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')'));
|
||||
return;
|
||||
}
|
||||
showGeneratedPassword(username, res.data.generatedPassword);
|
||||
loadAndRenderUsers();
|
||||
}).catch(err => alert('Verbindung zum Server fehlgeschlagen: ' + err.message));
|
||||
};
|
||||
|
||||
window.toggleUserActive = function(id, currentlyActive){
|
||||
const goingActive = !currentlyActive;
|
||||
if(!goingActive && !confirm('Benutzer wirklich deaktivieren? Bestehende Sitzungen werden sofort beendet.')) return;
|
||||
apiFetch('/admin/users/' + id, { method: 'PATCH', body: JSON.stringify({ active: goingActive }) }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
alert((res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')'));
|
||||
return;
|
||||
}
|
||||
loadAndRenderUsers();
|
||||
}).catch(err => alert('Verbindung zum Server fehlgeschlagen: ' + err.message));
|
||||
};
|
||||
|
||||
window.hardDeleteUser = function(id, username){
|
||||
if(!confirm('Benutzer "' + username + '" wirklich ENDGÜLTIG löschen? Das kann nicht rückgängig gemacht werden.')) return;
|
||||
apiFetch('/admin/users/' + id + '?hard=1', { method: 'DELETE' }).then(res => {
|
||||
if(!res.ok || !res.data || !res.data.ok){
|
||||
alert((res.data && res.data.error) || ('Fehler (HTTP ' + res.status + ')'));
|
||||
return;
|
||||
}
|
||||
loadAndRenderUsers();
|
||||
}).catch(err => alert('Verbindung zum Server fehlgeschlagen: ' + err.message));
|
||||
};
|
||||
|
||||
// personIdManuallyEdited wird gesetzt, sobald der Nutzer das Person-ID-Feld
|
||||
// selbst bearbeitet (nach dem Laden des DOM gebunden).
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
const personIdInput = document.getElementById('newUserPersonId');
|
||||
if(personIdInput){
|
||||
personIdInput.addEventListener('input', function(){ personIdManuallyEdited = true; });
|
||||
}
|
||||
const initialsInput = document.getElementById('newUserInitials');
|
||||
if(initialsInput){
|
||||
initialsInput.addEventListener('input', function(){ initialsManuallyEdited = true; });
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
Reference in new issue
Block a user