Files
SDSStundenerfassung/public/js/admin.js

662 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Admin JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Benutzer-Formular
const form = document.getElementById('addUserForm');
form.addEventListener('submit', async function(e) {
e.preventDefault();
// Rollen aus Checkboxen sammeln
const roleCheckboxes = document.querySelectorAll('input[name="roles"]:checked');
const roles = Array.from(roleCheckboxes).map(cb => cb.value);
// Validierung: Mindestens eine Rolle muss ausgewählt sein
if (roles.length === 0) {
alert('Bitte wählen Sie mindestens eine Rolle aus.');
return;
}
const defaultBreakInput = document.getElementById('defaultBreakMinutes');
const defaultBreakVal = defaultBreakInput && defaultBreakInput.value !== '' ? parseInt(defaultBreakInput.value, 10) : 30;
const default_break_minutes = (!isNaN(defaultBreakVal) && defaultBreakVal >= 0) ? defaultBreakVal : 30;
const formData = {
username: document.getElementById('username').value,
password: document.getElementById('password').value,
firstname: document.getElementById('firstname').value,
lastname: document.getElementById('lastname').value,
roles: roles,
personalnummer: document.getElementById('personalnummer').value,
wochenstunden: document.getElementById('wochenstunden').value,
arbeitstage: document.getElementById('arbeitstage').value,
urlaubstage: document.getElementById('urlaubstage').value,
default_break_minutes: default_break_minutes
};
try {
const response = await fetch('/admin/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
alert('Benutzer wurde erfolgreich angelegt!');
location.reload();
} else {
alert('Fehler: ' + (result.error || 'Benutzer konnte nicht angelegt werden'));
}
} catch (error) {
console.error('Fehler:', error);
alert('Fehler beim Anlegen des Benutzers');
}
});
// LDAP-Konfiguration laden
loadLDAPConfig();
// Optionen laden
loadOptions();
// MSSQL-Konfiguration laden
loadMssqlConfig();
// Timesheet-Duplikate Button
const loadTimesheetDuplicatesBtn = document.getElementById('loadTimesheetDuplicatesBtn');
if (loadTimesheetDuplicatesBtn) {
loadTimesheetDuplicatesBtn.addEventListener('click', function() {
loadTimesheetDuplicates();
});
}
// Optionen-Formular
const optionsForm = document.getElementById('optionsForm');
if (optionsForm) {
optionsForm.addEventListener('submit', async function(e) {
e.preventDefault();
const formData = {
saturday_percentage: document.getElementById('saturdayPercentage').value,
sunday_percentage: document.getElementById('sundayPercentage').value,
checkin_root_url: document.getElementById('checkinRootUrl') ? document.getElementById('checkinRootUrl').value : null
};
try {
const response = await fetch('/admin/options', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
alert('Optionen wurden erfolgreich gespeichert!');
} else {
alert('Fehler: ' + (result.error || 'Optionen konnten nicht gespeichert werden'));
}
} catch (error) {
console.error('Fehler:', error);
alert('Fehler beim Speichern der Optionen');
}
});
}
// LDAP-Konfigurationsformular
const ldapConfigForm = document.getElementById('ldapConfigForm');
if (ldapConfigForm) {
ldapConfigForm.addEventListener('submit', async function(e) {
e.preventDefault();
const enabled = document.getElementById('ldapEnabled').checked;
const url = document.getElementById('ldapUrl').value;
const baseDn = document.getElementById('ldapBaseDn').value;
// Validierung: URL und Base DN sind erforderlich wenn aktiviert
if (enabled && (!url || !baseDn)) {
alert('Bitte füllen Sie URL und Base DN aus, wenn LDAP aktiviert ist.');
return;
}
const formData = {
enabled: enabled,
url: url,
bind_dn: document.getElementById('ldapBindDn').value,
bind_password: document.getElementById('ldapBindPassword').value,
base_dn: baseDn,
user_search_filter: document.getElementById('ldapSearchFilter').value,
username_attribute: document.getElementById('ldapUsernameAttr').value,
firstname_attribute: document.getElementById('ldapFirstnameAttr').value,
lastname_attribute: document.getElementById('ldapLastnameAttr').value,
sync_interval: parseInt(document.getElementById('ldapSyncInterval').value) || 0
};
try {
const response = await fetch('/admin/ldap/config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
alert('LDAP-Konfiguration wurde erfolgreich gespeichert!');
location.reload();
} else {
alert('Fehler: ' + (result.error || 'Konfiguration konnte nicht gespeichert werden'));
}
} catch (error) {
console.error('Fehler:', error);
alert('Fehler beim Speichern der Konfiguration');
}
});
}
// MSSQL-Konfigurationsformular
const mssqlConfigForm = document.getElementById('mssqlConfigForm');
if (mssqlConfigForm) {
mssqlConfigForm.addEventListener('submit', async function(e) {
e.preventDefault();
const server = document.getElementById('mssqlServer').value;
const database = document.getElementById('mssqlDatabase').value;
const username = document.getElementById('mssqlUsername').value;
const password = document.getElementById('mssqlPassword').value;
if (!server || !database || !username) {
alert('Bitte Server, Datenbankname und Benutzername ausfüllen.');
return;
}
const formData = { server, database, username, password };
try {
const response = await fetch('/admin/mssql-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (result.success) {
alert('MSSQL-Konfiguration wurde erfolgreich gespeichert!');
// Passwort-Feld leeren nach dem Speichern
document.getElementById('mssqlPassword').value = '';
} else {
alert('Fehler: ' + (result.error || 'MSSQL-Konfiguration konnte nicht gespeichert werden'));
}
} catch (error) {
console.error('Fehler:', error);
alert('Fehler beim Speichern der MSSQL-Konfiguration');
}
});
}
// MSSQL Test-Verbindung
const mssqlTestBtn = document.getElementById('mssqlTestConnectionBtn');
if (mssqlTestBtn) {
mssqlTestBtn.addEventListener('click', async function() {
const statusEl = document.getElementById('mssqlTestStatus');
mssqlTestBtn.disabled = true;
if (statusEl) {
statusEl.textContent = 'Verbindung wird getestet...';
statusEl.style.color = 'blue';
}
try {
const response = await fetch('/admin/mssql-test-connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (response.ok && result && result.success) {
if (statusEl) {
statusEl.textContent = 'Verbindung erfolgreich.';
statusEl.style.color = 'green';
}
} else {
const msg = (result && result.error) ? result.error : 'Testverbindung fehlgeschlagen.';
if (statusEl) {
statusEl.textContent = msg;
statusEl.style.color = 'red';
}
alert(msg);
}
} catch (error) {
console.error('Fehler bei MSSQL-Testverbindung:', error);
if (statusEl) {
statusEl.textContent = 'Fehler bei der Testverbindung.';
statusEl.style.color = 'red';
}
alert('Fehler bei der Testverbindung zur MSSQL-Datenbank.');
} finally {
mssqlTestBtn.disabled = false;
}
});
}
// Sync-Button
const syncNowBtn = document.getElementById('syncNowBtn');
if (syncNowBtn) {
syncNowBtn.addEventListener('click', async function() {
if (!confirm('Möchten Sie die LDAP-Synchronisation jetzt starten?')) {
return;
}
const statusEl = document.getElementById('syncStatus');
syncNowBtn.disabled = true;
statusEl.textContent = 'Synchronisation läuft...';
statusEl.style.color = 'blue';
try {
const response = await fetch('/admin/ldap/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (result.success) {
statusEl.textContent = `Erfolgreich: ${result.synced} Benutzer synchronisiert`;
statusEl.style.color = 'green';
if (result.errors && result.errors.length > 0) {
alert('Synchronisation abgeschlossen mit Warnungen:\n' + result.errors.join('\n'));
} else {
alert(`Synchronisation erfolgreich abgeschlossen: ${result.synced} Benutzer synchronisiert`);
}
// Seite neu laden nach kurzer Verzögerung
setTimeout(() => {
location.reload();
}, 2000);
} else {
statusEl.textContent = 'Fehler: ' + (result.error || 'Synchronisation fehlgeschlagen');
statusEl.style.color = 'red';
alert('Fehler: ' + (result.error || 'Synchronisation fehlgeschlagen'));
syncNowBtn.disabled = false;
}
} catch (error) {
console.error('Fehler:', error);
statusEl.textContent = 'Fehler bei der Synchronisation';
statusEl.style.color = 'red';
alert('Fehler bei der Synchronisation');
syncNowBtn.disabled = false;
}
});
}
});
async function loadTimesheetDuplicates() {
const container = document.getElementById('timesheetDuplicatesContainer');
if (!container) return;
container.innerHTML = '<p style="color: #666;">Lade Timesheet-Duplikate...</p>';
try {
const response = await fetch('/admin/api/timesheet-duplicates');
const result = await response.json();
if (!response.ok) {
const msg = result && result.error ? result.error : 'Fehler beim Laden der Timesheet-Duplikate.';
container.innerHTML = `<p style="color: red;">${msg}</p>`;
return;
}
const groups = Array.isArray(result.groups) ? result.groups : [];
if (groups.length === 0) {
container.innerHTML = '<p style="color: green;">Es wurden keine doppelten Timesheet-Einträge gefunden. Alles sauber.</p>';
return;
}
let html = '';
groups.forEach((group, index) => {
const headerLabel = `${group.user_name || group.username || ('User #' + group.user_id)} ${group.date} (Anzahl Einträge: ${group.entry_count})`;
html += `
<div class="timesheet-duplicate-group" style="border: 1px solid #ddd; border-radius: 4px; margin-bottom: 15px;">
<div style="padding: 10px 15px; background-color: #f5f5f5; display: flex; justify-content: space-between; align-items: center;">
<div>
<strong>Gruppe ${index + 1}</strong>: ${headerLabel}
</div>
</div>
<div style="padding: 10px 15px; overflow-x: auto;">
<table style="width: 100%; min-width: 800px; border-collapse: collapse;">
<thead>
<tr>
<th style="text-align:left; padding:4px;">ID</th>
<th style="text-align:left; padding:4px;">Start</th>
<th style="text-align:left; padding:4px;">Ende</th>
<th style="text-align:left; padding:4px;">Pause (Min)</th>
<th style="text-align:left; padding:4px;">Stunden (total_hours)</th>
<th style="text-align:left; padding:4px;">Status</th>
<th style="text-align:left; padding:4px;">Erstellt</th>
<th style="text-align:left; padding:4px;">Aktualisiert</th>
<th style="text-align:left; padding:4px;">Aktionen</th>
</tr>
</thead>
<tbody>
`;
(group.entries || []).forEach(entry => {
const created = entry.created_at ? new Date(entry.created_at).toLocaleString('de-DE') : '-';
const updated = entry.updated_at ? new Date(entry.updated_at).toLocaleString('de-DE') : '-';
const totalHours = entry.total_hours != null ? entry.total_hours : '-';
const status = entry.status || '-';
const breakMinutes = entry.break_minutes != null ? entry.break_minutes : 0;
html += `
<tr>
<td style="padding:4px;">${entry.id}</td>
<td style="padding:4px;">${entry.start_time || '-'}</td>
<td style="padding:4px;">${entry.end_time || '-'}</td>
<td style="padding:4px;">${breakMinutes}</td>
<td style="padding:4px;">${totalHours}</td>
<td style="padding:4px;">${status}</td>
<td style="padding:4px;">${created}</td>
<td style="padding:4px;">${updated}</td>
<td style="padding:4px;">
<button type="button" class="btn btn-danger btn-sm" onclick="deleteTimesheetEntry(${entry.id})">
Eintrag löschen
</button>
</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
</div>
`;
});
container.innerHTML = html;
} catch (error) {
console.error('Fehler beim Laden der Timesheet-Duplikate:', error);
container.innerHTML = '<p style="color: red;">Fehler beim Laden der Timesheet-Duplikate.</p>';
}
}
async function deleteTimesheetEntry(entryId) {
try {
const response = await fetch(`/admin/api/timesheet-entry/${entryId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (response.ok && result && result.success) {
// Liste neu laden, um den aktuellen Stand anzuzeigen
loadTimesheetDuplicates();
} else {
const msg = (result && result.error) ? result.error : 'Timesheet-Eintrag konnte nicht gelöscht werden.';
alert('Fehler: ' + msg);
}
} catch (error) {
console.error('Fehler beim Löschen des Timesheet-Eintrags:', error);
alert('Fehler beim Löschen des Timesheet-Eintrags.');
}
}
// Optionen laden und Formular ausfüllen
async function loadOptions() {
try {
const response = await fetch('/admin/options');
const result = await response.json();
if (result.config) {
const config = result.config;
if (document.getElementById('saturdayPercentage')) {
document.getElementById('saturdayPercentage').value = config.saturday_percentage || 0;
}
if (document.getElementById('sundayPercentage')) {
document.getElementById('sundayPercentage').value = config.sunday_percentage || 0;
}
if (document.getElementById('checkinRootUrl')) {
document.getElementById('checkinRootUrl').value = config.checkin_root_url || '';
}
}
} catch (error) {
console.error('Fehler beim Laden der Optionen:', error);
}
}
// LDAP-Konfiguration laden und Formular ausfüllen
async function loadLDAPConfig() {
try {
const response = await fetch('/admin/ldap/config');
const result = await response.json();
if (result.config) {
const config = result.config;
if (document.getElementById('ldapEnabled')) {
document.getElementById('ldapEnabled').checked = config.enabled === 1;
}
if (document.getElementById('ldapUrl')) {
document.getElementById('ldapUrl').value = config.url || '';
}
if (document.getElementById('ldapBaseDn')) {
document.getElementById('ldapBaseDn').value = config.base_dn || '';
}
if (document.getElementById('ldapBindDn')) {
document.getElementById('ldapBindDn').value = config.bind_dn || '';
}
if (document.getElementById('ldapSearchFilter')) {
document.getElementById('ldapSearchFilter').value = config.user_search_filter || '(objectClass=person)';
}
if (document.getElementById('ldapUsernameAttr')) {
document.getElementById('ldapUsernameAttr').value = config.username_attribute || 'sAMAccountName';
}
if (document.getElementById('ldapFirstnameAttr')) {
document.getElementById('ldapFirstnameAttr').value = config.firstname_attribute || 'givenName';
}
if (document.getElementById('ldapLastnameAttr')) {
document.getElementById('ldapLastnameAttr').value = config.lastname_attribute || 'sn';
}
if (document.getElementById('ldapSyncInterval')) {
document.getElementById('ldapSyncInterval').value = config.sync_interval || 0;
}
}
} catch (error) {
console.error('Fehler beim Laden der LDAP-Konfiguration:', error);
}
}
// MSSQL-Konfiguration laden und Formular ausfüllen
async function loadMssqlConfig() {
try {
const response = await fetch('/admin/mssql-config');
const result = await response.json();
if (result.config) {
const config = result.config;
if (document.getElementById('mssqlServer')) {
document.getElementById('mssqlServer').value = config.server || '';
}
if (document.getElementById('mssqlDatabase')) {
document.getElementById('mssqlDatabase').value = config.database || '';
}
if (document.getElementById('mssqlUsername')) {
document.getElementById('mssqlUsername').value = config.username || '';
}
// Passwort wird aus Sicherheitsgründen nie vorausgefüllt
}
} catch (error) {
console.error('Fehler beim Laden der MSSQL-Konfiguration:', error);
}
}
async function deleteUser(userId, username) {
const confirmed = confirm(`Möchten Sie den Benutzer "${username}" wirklich löschen?`);
if (!confirmed) return;
try {
const response = await fetch(`/admin/users/${userId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('Benutzer wurde erfolgreich gelöscht!');
location.reload();
} else {
alert('Fehler: ' + (result.error || 'Benutzer konnte nicht gelöscht werden'));
}
} catch (error) {
console.error('Fehler:', error);
alert('Fehler beim Löschen des Benutzers');
}
}
// User bearbeiten
function editUser(userId) {
const row = document.querySelector(`tr[data-user-id="${userId}"]`);
if (!row) return;
// Alle Display-Felder ausblenden und Edit-Felder einblenden
row.querySelectorAll('.user-field-display').forEach(display => {
display.style.display = 'none';
});
row.querySelectorAll('.user-field-edit').forEach(edit => {
edit.style.display = 'inline-block';
});
// Buttons umschalten
row.querySelector('.edit-user-btn').style.display = 'none';
row.querySelector('.save-user-btn').style.display = 'inline-block';
row.querySelector('.cancel-user-btn').style.display = 'inline-block';
}
// User speichern
async function saveUser(userId) {
const row = document.querySelector(`tr[data-user-id="${userId}"]`);
if (!row) return;
const personalnummer = row.querySelector('input[data-field="personalnummer"]').value;
const wochenstunden = row.querySelector('input[data-field="wochenstunden"]').value;
const arbeitstage = row.querySelector('input[data-field="arbeitstage"]').value;
const urlaubstage = row.querySelector('input[data-field="urlaubstage"]').value;
const defaultBreakInput = row.querySelector('input[data-field="default_break_minutes"]');
const default_break_minutes = defaultBreakInput && defaultBreakInput.value !== '' ? parseInt(defaultBreakInput.value, 10) : 30;
const normalizedDefaultBreak = (!isNaN(default_break_minutes) && default_break_minutes >= 0) ? default_break_minutes : 30;
// Rollen aus Checkboxen sammeln
const roleCheckboxes = row.querySelectorAll('.role-checkbox:checked');
const roles = Array.from(roleCheckboxes).map(cb => cb.value);
// Validierung: Mindestens eine Rolle erforderlich
if (roles.length === 0) {
alert('Mindestens eine Rolle muss ausgewählt sein.');
return;
}
try {
const response = await fetch(`/admin/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
personalnummer: personalnummer || null,
wochenstunden: wochenstunden || null,
arbeitstage: arbeitstage || 5,
urlaubstage: urlaubstage || null,
default_break_minutes: normalizedDefaultBreak,
roles: roles
})
});
const result = await response.json();
if (result.success) {
// Werte in Display-Felder übernehmen
row.querySelector('span[data-field="personalnummer"]').textContent = personalnummer || '-';
row.querySelector('span[data-field="wochenstunden"]').textContent = wochenstunden || '-';
row.querySelector('span[data-field="urlaubstage"]').textContent = urlaubstage || '-';
const defaultBreakDisplay = row.querySelector('span[data-field="default_break_minutes"]');
if (defaultBreakDisplay) defaultBreakDisplay.textContent = normalizedDefaultBreak;
// Rollen-Display aktualisieren
const rolesDisplay = row.querySelector('div[data-field="roles"]');
if (rolesDisplay) {
const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Admin' };
rolesDisplay.innerHTML = roles.map(role =>
`<span class="role-badge role-${role}" style="margin-right: 5px;">${roleLabels[role] || role}</span>`
).join('');
}
// Bearbeitung beenden
cancelEditUser(userId);
alert('Benutzerdaten wurden erfolgreich gespeichert!');
// Seite neu laden um sicherzustellen dass alles korrekt ist
location.reload();
} else {
alert('Fehler: ' + (result.error || 'Daten konnten nicht gespeichert werden'));
}
} catch (error) {
console.error('Fehler:', error);
alert('Fehler beim Speichern der Benutzerdaten');
}
}
// Bearbeitung abbrechen
function cancelEditUser(userId) {
const row = document.querySelector(`tr[data-user-id="${userId}"]`);
if (!row) return;
// Alle Edit-Felder ausblenden und Display-Felder einblenden
row.querySelectorAll('.user-field-edit').forEach(edit => {
edit.style.display = 'none';
// Wert zurücksetzen (nur für Input-Felder, nicht für Rollen)
const field = edit.dataset.field;
if (field !== 'roles') {
const display = row.querySelector(`span[data-field="${field}"]`);
if (display && edit.tagName === 'INPUT') {
edit.value = display.textContent === '-' ? '' : display.textContent;
}
}
});
row.querySelectorAll('.user-field-display').forEach(display => {
if (display.tagName === 'DIV' || display.tagName === 'SPAN') {
display.style.display = 'block';
} else {
display.style.display = 'inline';
}
});
// Buttons umschalten
row.querySelector('.edit-user-btn').style.display = 'inline-block';
row.querySelector('.save-user-btn').style.display = 'none';
row.querySelector('.cancel-user-btn').style.display = 'none';
}