Sortieungsoptionen in Verwaltung, Umstellung der PDF generation auf generierung bei abgabe und ablage auf dem Sateisystem um einen Festen Stand zu garantieren
This commit is contained in:
@@ -73,6 +73,45 @@
|
||||
<div id="bulkDownloadStatus" style="margin-top: 12px; font-size: 13px; color: #666; display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Sortierung der Mitarbeiter-Gruppen -->
|
||||
<div style="margin-bottom: 25px; padding: 12px 16px; background-color: #f8f9fa; border-radius: 8px; border: 1px solid #dee2e6;">
|
||||
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 10px;">
|
||||
<span style="font-size: 13px; color: #555; font-weight: 500; margin-right: 5px;">Sortieren nach:</span>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="firstname" data-sort-direction="asc">
|
||||
Vorname ▲
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="firstname" data-sort-direction="desc">
|
||||
Vorname ▼
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="name" data-sort-direction="asc">
|
||||
Name ▲
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="name" data-sort-direction="desc">
|
||||
Name ▼
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="personalnummer" data-sort-direction="asc">
|
||||
Personalnr. ▲
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="personalnummer" data-sort-direction="desc">
|
||||
Personalnr. ▼
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="overtime" data-sort-direction="asc">
|
||||
Aktuelle Überstunden ▲
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="overtime" data-sort-direction="desc">
|
||||
Aktuelle Überstunden ▼
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="remainingVacation" data-sort-direction="asc">
|
||||
Verbleibender Urlaub ▲
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm timesheet-sort-btn" data-sort-field="remainingVacation" data-sort-direction="desc">
|
||||
Verbleibender Urlaub ▼
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (!groupedByEmployee || groupedByEmployee.length === 0) { %>
|
||||
<div class="empty-state">
|
||||
<p>Keine eingereichten Stundenzettel vorhanden.</p>
|
||||
@@ -81,7 +120,14 @@
|
||||
<div class="timesheet-groups">
|
||||
<% groupedByEmployee.forEach(function(employee, employeeIndex) { %>
|
||||
<!-- Level 1: Mitarbeiter -->
|
||||
<div class="employee-group" data-employee-id="<%= employee.user.id %>" data-employee-index="<%= employeeIndex %>">
|
||||
<div
|
||||
class="employee-group"
|
||||
data-employee-id="<%= employee.user.id %>"
|
||||
data-employee-index="<%= employeeIndex %>"
|
||||
data-lastname="<%= employee.user.lastname || '' %>"
|
||||
data-firstname="<%= employee.user.firstname || '' %>"
|
||||
data-personalnummer="<%= employee.user.personalnummer || '' %>"
|
||||
>
|
||||
<div class="employee-header">
|
||||
<div class="employee-info">
|
||||
<div class="employee-name">
|
||||
@@ -513,9 +559,16 @@
|
||||
if (value === null) {
|
||||
el.textContent = '-';
|
||||
el.style.color = '';
|
||||
el.dataset.overtimeValue = '';
|
||||
} else {
|
||||
el.textContent = (value >= 0 ? '+' : '') + formatHoursMin(value);
|
||||
el.style.color = value >= 0 ? '#27ae60' : '#e74c3c';
|
||||
el.dataset.overtimeValue = String(value);
|
||||
}
|
||||
|
||||
const group = document.querySelector(`.employee-group[data-employee-id="${userId}"]`);
|
||||
if (group) {
|
||||
group.dataset.overtimeValue = el.dataset.overtimeValue || '';
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -523,6 +576,7 @@
|
||||
elements.forEach(el => {
|
||||
el.textContent = '-';
|
||||
el.style.color = '';
|
||||
el.dataset.overtimeValue = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -573,6 +627,12 @@
|
||||
} else {
|
||||
el.textContent = value.toFixed(1);
|
||||
}
|
||||
// Wert für Sortierung am Element und an der Mitarbeiter-Gruppe hinterlegen
|
||||
el.dataset.remainingVacation = value !== null ? String(value) : '';
|
||||
const group = el.closest('.employee-group') || document.querySelector(`.employee-group[data-employee-id="${el.dataset.userId}"]`);
|
||||
if (group) {
|
||||
group.dataset.remainingVacation = value !== null ? String(value) : '';
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden des verbleibenden Urlaubs für User', userId, err);
|
||||
@@ -585,6 +645,91 @@
|
||||
loadSickDays();
|
||||
loadCurrentOvertime();
|
||||
loadRemainingVacation();
|
||||
|
||||
function sortEmployeeGroups(field, direction) {
|
||||
const container = document.querySelector('.timesheet-groups');
|
||||
if (!container) return;
|
||||
const groups = Array.from(container.querySelectorAll('.employee-group'));
|
||||
if (groups.length === 0) return;
|
||||
const dir = direction === 'desc' ? -1 : 1;
|
||||
|
||||
function compareStrings(a, b) {
|
||||
const sa = (a || '').toString().toLowerCase();
|
||||
const sb = (b || '').toString().toLowerCase();
|
||||
return sa.localeCompare(sb, 'de-DE');
|
||||
}
|
||||
|
||||
function parseNumberOrNull(value) {
|
||||
if (value === null || value === undefined || value === '') return null;
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
groups.sort((a, b) => {
|
||||
let va;
|
||||
let vb;
|
||||
|
||||
if (field === 'name') {
|
||||
const aLast = a.dataset.lastname || '';
|
||||
const aFirst = a.dataset.firstname || '';
|
||||
const bLast = b.dataset.lastname || '';
|
||||
const bFirst = b.dataset.firstname || '';
|
||||
const cmp = compareStrings(`${aLast} ${aFirst}`, `${bLast} ${bFirst}`);
|
||||
return cmp * dir;
|
||||
}
|
||||
|
||||
if (field === 'firstname') {
|
||||
const aFirst = a.dataset.firstname || '';
|
||||
const bFirst = b.dataset.firstname || '';
|
||||
const firstCmp = compareStrings(aFirst, bFirst);
|
||||
if (firstCmp !== 0) return firstCmp * dir;
|
||||
// Bei gleichem Vornamen nach Nachname sortieren
|
||||
const aLast = a.dataset.lastname || '';
|
||||
const bLast = b.dataset.lastname || '';
|
||||
const lastCmp = compareStrings(aLast, bLast);
|
||||
return lastCmp * dir;
|
||||
}
|
||||
|
||||
if (field === 'personalnummer') {
|
||||
const aRaw = a.dataset.personalnummer || '';
|
||||
const bRaw = b.dataset.personalnummer || '';
|
||||
|
||||
if (!aRaw && !bRaw) return 0;
|
||||
if (!aRaw) return 1;
|
||||
if (!bRaw) return -1;
|
||||
|
||||
const digitsOnly = /^\d+$/;
|
||||
const aIsNum = digitsOnly.test(aRaw);
|
||||
const bIsNum = digitsOnly.test(bRaw);
|
||||
|
||||
if (aIsNum && bIsNum) {
|
||||
va = Number(aRaw);
|
||||
vb = Number(bRaw);
|
||||
if (va === vb) return 0;
|
||||
return va < vb ? -1 * dir : 1 * dir;
|
||||
}
|
||||
|
||||
const cmp = compareStrings(aRaw, bRaw);
|
||||
return cmp * dir;
|
||||
}
|
||||
|
||||
if (field === 'overtime') {
|
||||
va = parseNumberOrNull(a.dataset.overtimeValue);
|
||||
vb = parseNumberOrNull(b.dataset.overtimeValue);
|
||||
} else if (field === 'remainingVacation') {
|
||||
va = parseNumberOrNull(a.dataset.remainingVacation);
|
||||
vb = parseNumberOrNull(b.dataset.remainingVacation);
|
||||
}
|
||||
|
||||
if (va === null && vb === null) return 0;
|
||||
if (va === null) return 1;
|
||||
if (vb === null) return -1;
|
||||
if (va === vb) return 0;
|
||||
return va < vb ? -1 * dir : 1 * dir;
|
||||
});
|
||||
|
||||
groups.forEach(group => container.appendChild(group));
|
||||
}
|
||||
|
||||
// Überstunden-Korrektur-Historie laden/anzeigen
|
||||
function parseSqliteDatetime(value) {
|
||||
@@ -1057,6 +1202,30 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sortier-Buttons für Mitarbeiter-Gruppen
|
||||
(function initTimesheetGroupSorting() {
|
||||
const sortButtons = document.querySelectorAll('.timesheet-sort-btn');
|
||||
if (!sortButtons.length) return;
|
||||
|
||||
let activeSortButton = null;
|
||||
|
||||
sortButtons.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const field = this.dataset.sortField;
|
||||
const direction = this.dataset.sortDirection || 'asc';
|
||||
if (!field) return;
|
||||
|
||||
sortEmployeeGroups(field, direction);
|
||||
|
||||
if (activeSortButton && activeSortButton !== this) {
|
||||
activeSortButton.classList.remove('active-sort');
|
||||
}
|
||||
this.classList.add('active-sort');
|
||||
activeSortButton = this;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
// Rollenwechsel-Handler
|
||||
|
||||
Reference in New Issue
Block a user