V1.1 Verschiedene Anpassungen

This commit is contained in:
2026-02-03 21:32:20 +01:00
parent 952c353118
commit 4be9a365b3
18 changed files with 1068 additions and 114 deletions

View File

@@ -111,6 +111,25 @@
Speichern
</button>
</div>
<div style="display: inline-flex; gap: 8px; align-items: center; margin-right: 20px;">
<strong>Urlaubstage-Offset:</strong>
<input
type="number"
step="0.5"
class="vacation-offset-input"
data-user-id="<%= employee.user.id %>"
value="<%= (employee.user.vacation_offset_days !== undefined && employee.user.vacation_offset_days !== null) ? employee.user.vacation_offset_days : 0 %>"
style="width: 90px; padding: 4px 6px; border: 1px solid #ddd; border-radius: 4px;"
title="Manuelle Korrektur (positiv oder negativ) in Tagen" />
<button
type="button"
class="btn btn-success btn-sm save-vacation-offset-btn"
data-user-id="<%= employee.user.id %>"
style="padding: 6px 10px; white-space: nowrap;"
title="Urlaubstage-Offset speichern">
Speichern
</button>
</div>
<div style="display: inline-block; margin-right: 20px;">
<strong>Kalenderwochen:</strong> <span><%= employee.weeks.length %></span>
</div>
@@ -154,6 +173,11 @@
<div class="week-versions-info" style="margin-top: 5px;">
<span class="version-count"><%= week.total_versions %> Version<%= week.total_versions !== 1 ? 'en' : '' %></span>
</div>
<% if (week.has_new_version_after_download) { %>
<div class="new-version-warning" style="margin-top: 10px;">
<strong>ACHTUNG: Neue version eingereicht</strong>
</div>
<% } %>
</div>
<button class="btn btn-secondary btn-sm toggle-versions-btn" data-employee-index="<%= employeeIndex %>" data-week-index="<%= weekIndex %>">
<span class="toggle-icon">▼</span> Versionen
@@ -169,7 +193,6 @@
<th>Eingereicht am</th>
<th>Grund</th>
<th>Kommentar</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
@@ -225,7 +248,6 @@
</button>
</div>
</td>
<td><span class="status-badge status-<%= ts.status %>"><%= ts.status %></span></td>
<td>
<button class="btn btn-info btn-sm toggle-pdf-btn" data-timesheet-id="<%= ts.id %>">
<span class="arrow-icon">▶</span> PDF anzeigen
@@ -236,7 +258,7 @@
</td>
</tr>
<tr class="pdf-preview-row" data-timesheet-id="<%= ts.id %>" style="display: none;">
<td colspan="6">
<td colspan="5">
<div class="pdf-preview-container">
<div class="pdf-preview-header">
<h3>PDF-Vorschau: <%= employee.user.firstname %> <%= employee.user.lastname %> - Version <%= ts.version || 1 %> - <%= new Date(ts.week_start).toLocaleDateString('de-DE') %> bis <%= new Date(ts.week_end).toLocaleDateString('de-DE') %></h3>
@@ -317,6 +339,12 @@
${data.remainingVacation !== undefined ? `<span style="color: #28a745;">(verbleibend: ${data.remainingVacation.toFixed(1)} Tage)</span>` : ''}
</div>`;
}
if (data.vacationOffsetDays !== undefined && data.vacationOffsetDays !== 0) {
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
<strong>Urlaubstage-Offset:</strong> <span>${Number(data.vacationOffsetDays).toFixed(1)} Tag${Math.abs(data.vacationOffsetDays) !== 1 ? 'e' : ''}</span>
${data.remainingVacation !== undefined ? `<span style="color: #28a745;">(verbleibend inkl. Offset: ${Number(data.remainingVacation).toFixed(1)} Tage)</span>` : ''}
</div>`;
}
if (data.sickDays !== undefined && data.sickDays > 0) {
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
<strong>Krankheitstage:</strong> <span style="color: #e74c3c;">${data.sickDays} Tag${data.sickDays !== 1 ? 'e' : ''}</span>
@@ -431,6 +459,68 @@
}
});
});
// Urlaubstage-Offset speichern
document.querySelectorAll('.save-vacation-offset-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const userId = this.dataset.userId;
const input = document.querySelector(`.vacation-offset-input[data-user-id="${userId}"]`);
if (!input) return;
const originalText = this.textContent;
this.disabled = true;
this.textContent = '...';
// leere Eingabe => 0 (Backend macht das auch, aber UI soll sauber sein)
const raw = (input.value || '').trim();
const value = raw === '' ? '' : Number(raw);
try {
const resp = await fetch(`/api/verwaltung/user/${userId}/vacation-offset`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ vacation_offset_days: value })
});
const data = await resp.json();
if (!resp.ok) {
alert(data.error || 'Fehler beim Speichern des Offsets');
return;
}
// Normalisiere Input auf Zahl (Backend gibt number zurück)
input.value = (data.vacation_offset_days !== undefined && data.vacation_offset_days !== null)
? Number(data.vacation_offset_days)
: 0;
// Stats für diesen User neu laden
const statDivs = document.querySelectorAll(`.group-stats[data-user-id="${userId}"]`);
statDivs.forEach(div => {
// loading indicator optional wieder anzeigen
const loading = div.querySelector('.stats-loading');
if (loading) {
loading.style.display = 'inline-block';
loading.style.color = '#666';
loading.textContent = 'Lade Statistiken...';
}
loadStatsForDiv(div);
});
this.textContent = '✓';
setTimeout(() => {
this.textContent = originalText;
this.disabled = false;
}, 900);
} catch (e) {
console.error('Fehler beim Speichern des Offsets:', e);
alert('Fehler beim Speichern des Offsets');
} finally {
if (this.textContent === '...') {
this.textContent = originalText;
this.disabled = false;
}
}
});
});
// Mitarbeiter-Gruppen auf-/zuklappen (zeigt/versteckt Wochen)
document.querySelectorAll('.toggle-employee-btn').forEach(btn => {