Gehe zu button, Verwaltung Urlaubsberechnung
This commit is contained in:
@@ -383,9 +383,11 @@ body {
|
|||||||
|
|
||||||
.week-selector {
|
.week-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.week-selector h2 {
|
.week-selector h2 {
|
||||||
@@ -394,6 +396,30 @@ body {
|
|||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.week-selector-goto {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.week-selector-goto .form-control {
|
||||||
|
width: 200px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.go-to-week-error {
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 13px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Timesheet Table */
|
/* Timesheet Table */
|
||||||
.timesheet-grid {
|
.timesheet-grid {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|||||||
@@ -171,6 +171,56 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
saveLastWeek();
|
saveLastWeek();
|
||||||
loadWeek();
|
loadWeek();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showGoToWeekError(msg) {
|
||||||
|
const el = document.getElementById('goToWeekError');
|
||||||
|
if (el) { el.textContent = msg; el.style.display = 'inline'; }
|
||||||
|
}
|
||||||
|
function clearGoToWeekError() {
|
||||||
|
const el = document.getElementById('goToWeekError');
|
||||||
|
if (el) { el.textContent = ''; el.style.display = 'none'; }
|
||||||
|
}
|
||||||
|
function goToWeek() {
|
||||||
|
clearGoToWeekError();
|
||||||
|
const input = document.getElementById('goToWeekInput');
|
||||||
|
if (!input) return;
|
||||||
|
const raw = (input.value || '').trim();
|
||||||
|
if (!raw) {
|
||||||
|
showGoToWeekError('Bitte KW eingeben (z. B. 12 oder 2025 12).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let year = null;
|
||||||
|
let week = null;
|
||||||
|
const parts = raw.split(/[\s\/]+/).filter(Boolean).map(p => parseInt(p, 10));
|
||||||
|
if (parts.length === 1 && !isNaN(parts[0])) {
|
||||||
|
week = parts[0];
|
||||||
|
year = currentWeekStart ? parseInt(currentWeekStart.split('-')[0], 10) : new Date().getFullYear();
|
||||||
|
} else if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
|
||||||
|
if (parts[0] >= 1000) {
|
||||||
|
year = parts[0];
|
||||||
|
week = parts[1];
|
||||||
|
} else {
|
||||||
|
week = parts[0];
|
||||||
|
year = parts[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (year == null || week == null || week < 1 || week > 53 || year < 2000 || year > 2100) {
|
||||||
|
showGoToWeekError('Ungültige Eingabe. KW 1–53, Jahr 2000–2100 (z. B. 12 oder 2025 12).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentWeekStart = getWeekStartFromYearAndWeek(year, week);
|
||||||
|
saveLastWeek();
|
||||||
|
loadWeek();
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToWeekBtn = document.getElementById('goToWeekBtn');
|
||||||
|
if (goToWeekBtn) goToWeekBtn.addEventListener('click', goToWeek);
|
||||||
|
const goToWeekInput = document.getElementById('goToWeekInput');
|
||||||
|
if (goToWeekInput) {
|
||||||
|
goToWeekInput.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Enter') { e.preventDefault(); goToWeek(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Event-Listener für Submit-Button - mit Event-Delegation für bessere Zuverlässigkeit
|
// Event-Listener für Submit-Button - mit Event-Delegation für bessere Zuverlässigkeit
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
@@ -285,6 +335,20 @@ function getCalendarWeek(dateStr) {
|
|||||||
return weekNo;
|
return weekNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Montag (week_start) aus Jahr und Kalenderwoche (ISO 8601)
|
||||||
|
function getWeekStartFromYearAndWeek(year, weekNumber) {
|
||||||
|
const jan4 = new Date(Date.UTC(year, 0, 4));
|
||||||
|
const jan4Day = jan4.getUTCDay() || 7;
|
||||||
|
const daysToMonday = jan4Day === 1 ? 0 : 1 - jan4Day;
|
||||||
|
const firstMonday = new Date(Date.UTC(year, 0, 4 + daysToMonday));
|
||||||
|
const weekMonday = new Date(firstMonday);
|
||||||
|
weekMonday.setUTCDate(firstMonday.getUTCDate() + (weekNumber - 1) * 7);
|
||||||
|
const y = weekMonday.getUTCFullYear();
|
||||||
|
const m = String(weekMonday.getUTCMonth() + 1).padStart(2, '0');
|
||||||
|
const d = String(weekMonday.getUTCDate()).padStart(2, '0');
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Datum formatieren (YYYY-MM-DD)
|
// Datum formatieren (YYYY-MM-DD)
|
||||||
function formatDate(date) {
|
function formatDate(date) {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
|
|||||||
@@ -241,19 +241,16 @@ function registerVerwaltungRoutes(app) {
|
|||||||
return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' });
|
return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Für jede Woche die neuesten Einträge abrufen
|
// Nur Wochen bis Ende der angezeigten Kalenderwoche (Stand Urlaub = Ende dieser KW)
|
||||||
|
const weeksUpToDisplayed = (weeks || []).filter((w) => w.week_end <= week_end);
|
||||||
let processedWeeks = 0;
|
let processedWeeks = 0;
|
||||||
let totalVacationDays = 0;
|
let totalVacationDays = 0;
|
||||||
const vacationByDate = {};
|
const vacationByDate = {};
|
||||||
|
|
||||||
if (!weeks || weeks.length === 0) {
|
if (weeksUpToDisplayed.length === 0) {
|
||||||
// Keine eingereichten Wochen - setze totalVacationDays auf 0
|
|
||||||
totalVacationDays = 0;
|
|
||||||
// Weiter mit der normalen Verarbeitung der aktuellen Woche
|
|
||||||
processCurrentWeek(0);
|
processCurrentWeek(0);
|
||||||
} else {
|
} else {
|
||||||
weeks.forEach((week) => {
|
weeksUpToDisplayed.forEach((week) => {
|
||||||
// Einträge für diese Woche abrufen (nur neueste pro Tag)
|
|
||||||
db.all(`SELECT date, vacation_type, updated_at, id
|
db.all(`SELECT date, vacation_type, updated_at, id
|
||||||
FROM timesheet_entries
|
FROM timesheet_entries
|
||||||
WHERE user_id = ? AND date >= ? AND date <= ?
|
WHERE user_id = ? AND date >= ? AND date <= ?
|
||||||
@@ -266,13 +263,11 @@ function registerVerwaltungRoutes(app) {
|
|||||||
return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' });
|
return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtere auf neuesten Eintrag pro Tag
|
|
||||||
(weekEntries || []).forEach(entry => {
|
(weekEntries || []).forEach(entry => {
|
||||||
const existing = vacationByDate[entry.date];
|
const existing = vacationByDate[entry.date];
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
vacationByDate[entry.date] = entry;
|
vacationByDate[entry.date] = entry;
|
||||||
} else {
|
} else {
|
||||||
// Vergleiche updated_at (falls vorhanden) oder id (höhere ID = neuer)
|
|
||||||
const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0;
|
const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0;
|
||||||
const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0;
|
const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0;
|
||||||
if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) {
|
if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) {
|
||||||
@@ -282,8 +277,7 @@ function registerVerwaltungRoutes(app) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
processedWeeks++;
|
processedWeeks++;
|
||||||
if (processedWeeks === weeks.length) {
|
if (processedWeeks === weeksUpToDisplayed.length) {
|
||||||
// Alle Wochen verarbeitet - summiere Urlaubstage
|
|
||||||
Object.values(vacationByDate).forEach(entry => {
|
Object.values(vacationByDate).forEach(entry => {
|
||||||
if (entry.vacation_type === 'full') {
|
if (entry.vacation_type === 'full') {
|
||||||
totalVacationDays += 1;
|
totalVacationDays += 1;
|
||||||
@@ -291,8 +285,6 @@ function registerVerwaltungRoutes(app) {
|
|||||||
totalVacationDays += 0.5;
|
totalVacationDays += 0.5;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Weiter mit der normalen Verarbeitung der aktuellen Woche
|
|
||||||
processCurrentWeek(totalVacationDays);
|
processCurrentWeek(totalVacationDays);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,11 @@
|
|||||||
<button id="prevWeek" class="btn btn-secondary">◀ Vorherige Woche</button>
|
<button id="prevWeek" class="btn btn-secondary">◀ Vorherige Woche</button>
|
||||||
<h2 id="weekTitle">Kalenderwoche</h2>
|
<h2 id="weekTitle">Kalenderwoche</h2>
|
||||||
<button id="nextWeek" class="btn btn-secondary">Nächste Woche ▶</button>
|
<button id="nextWeek" class="btn btn-secondary">Nächste Woche ▶</button>
|
||||||
|
<div class="week-selector-goto">
|
||||||
|
<input type="text" id="goToWeekInput" placeholder="z. B. 12 oder 2025 12" class="form-control" />
|
||||||
|
<button type="button" id="goToWeekBtn" class="btn btn-secondary">Gehe zu KW</button>
|
||||||
|
<span id="goToWeekError" class="go-to-week-error" aria-live="polite"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="timesheetTable">
|
<div id="timesheetTable">
|
||||||
|
|||||||
@@ -334,10 +334,13 @@
|
|||||||
${data.remainingOvertimeWithOffset !== undefined ? `<span style="color: #28a745;">(verbleibend inkl. Offset: ${Number(data.remainingOvertimeWithOffset).toFixed(2)} h)</span>` : ''}
|
${data.remainingOvertimeWithOffset !== undefined ? `<span style="color: #28a745;">(verbleibend inkl. Offset: ${Number(data.remainingOvertimeWithOffset).toFixed(2)} h)</span>` : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
if (data.vacationDays !== undefined) {
|
if (data.totalVacationDays !== undefined || data.vacationDays !== undefined) {
|
||||||
|
const totalTaken = data.totalVacationDays !== undefined ? data.totalVacationDays : 0;
|
||||||
|
const inWeek = data.vacationDays !== undefined ? data.vacationDays : 0;
|
||||||
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
|
statsHTML += `<div class="stats-inline" style="display: inline-block; margin-right: 20px;">
|
||||||
<strong>Urlaub genommen:</strong> <span>${data.vacationDays.toFixed(1)} Tag${data.vacationDays !== 1 ? 'e' : ''}</span>
|
<strong>Urlaub genommen (kumuliert bis Ende KW):</strong> <span>${Number(totalTaken).toFixed(1)} Tag${totalTaken !== 1 ? 'e' : ''}</span>
|
||||||
${data.remainingVacation !== undefined ? `<span style="color: #28a745;">(verbleibend: ${data.remainingVacation.toFixed(1)} Tage)</span>` : ''}
|
${inWeek > 0 ? ` <span style="color: #666;">(davon in dieser Woche: ${inWeek.toFixed(1)})</span>` : ''}
|
||||||
|
${data.remainingVacation !== undefined ? ` <span style="color: #28a745;">(verbleibend Stand Ende KW: ${Number(data.remainingVacation).toFixed(1)} Tage)</span>` : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
if (data.vacationOffsetDays !== undefined && data.vacationOffsetDays !== 0) {
|
if (data.vacationOffsetDays !== undefined && data.vacationOffsetDays !== 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user