diff --git a/routes/verwaltung-routes.js b/routes/verwaltung-routes.js
index 9b7860e..4764dcc 100644
--- a/routes/verwaltung-routes.js
+++ b/routes/verwaltung-routes.js
@@ -44,7 +44,7 @@ function registerVerwaltungRoutes(app) {
}
// Gruppiere nach Mitarbeiter, dann nach Kalenderwoche
- // Struktur: { [user_id]: { user: {...}, weeks: { [week_key]: {...} } } }
+ // Struktur intern: { [user_id]: { user: {...}, weeks: { [week_key]: {...} } } }
const groupedByEmployee = {};
(timesheets || []).forEach(ts => {
@@ -86,7 +86,6 @@ function registerVerwaltungRoutes(app) {
// Prüfe für jede Woche, ob nach dem letzten Download eine neue Version eingereicht wurde
Object.values(groupedByEmployee).forEach(employee => {
Object.values(employee.weeks).forEach(week => {
- // Finde die neueste Version mit pdf_downloaded_at (letzter Download)
let lastDownloadTime = null;
week.versions.forEach(version => {
if (version.pdf_downloaded_at) {
@@ -96,8 +95,7 @@ function registerVerwaltungRoutes(app) {
}
}
});
-
- // Prüfe, ob es eine Version gibt, die nach dem letzten Download eingereicht wurde
+
let hasNewVersionAfterDownload = false;
if (lastDownloadTime) {
week.versions.forEach(version => {
@@ -109,26 +107,29 @@ function registerVerwaltungRoutes(app) {
}
});
}
-
- // Setze Flag auf dem week-Objekt
+
week.has_new_version_after_download = hasNewVersionAfterDownload;
});
});
-
+
// Sortierung: Mitarbeiter nach Name, Wochen nach Datum (neueste zuerst)
- const sortedEmployees = Object.values(groupedByEmployee).map(employee => {
- // Wochen innerhalb jedes Mitarbeiters sortieren
+ // Für das Initial-Rendering der Verwaltung werden nur Mitarbeiter-Header-Daten
+ // benötigt. Wochen-/Versionslisten werden per AJAX nachgeladen.
+ const employeesForView = Object.values(groupedByEmployee).map(employee => {
const sortedWeeks = Object.values(employee.weeks).sort((a, b) => {
return new Date(b.week_start) - new Date(a.week_start);
});
- // Flag: Gibt es in irgendeiner Woche eine neue Version nach Download?
const hasNewVersionAfterDownload = sortedWeeks.some(w => w.has_new_version_after_download);
-
+ const weekCount = sortedWeeks.length;
+ const latestWeek = weekCount > 0 ? sortedWeeks[0] : null;
+
return {
- ...employee,
+ user: employee.user,
has_new_version_after_download: hasNewVersionAfterDownload,
- weeks: sortedWeeks
+ weekCount,
+ latest_week_start: latestWeek ? latestWeek.week_start : null,
+ latest_week_end: latestWeek ? latestWeek.week_end : null
};
}).sort((a, b) => {
// Mitarbeiter nach Nachname, dann Vorname sortieren
@@ -138,7 +139,7 @@ function registerVerwaltungRoutes(app) {
});
res.render('verwaltung', {
- groupedByEmployee: sortedEmployees,
+ groupedByEmployee: employeesForView,
user: {
firstname: req.session.firstname,
lastname: req.session.lastname,
@@ -149,6 +150,122 @@ function registerVerwaltungRoutes(app) {
});
});
+ // API: Wochen + Versionen für einen Mitarbeiter (für Lazy-Loading in der Verwaltung)
+ app.get('/api/verwaltung/employee/:id/weeks', requireVerwaltung, (req, res) => {
+ const userId = parseInt(req.params.id, 10);
+ if (!Number.isFinite(userId)) {
+ return res.status(400).send('Ungültige User-ID');
+ }
+
+ db.all(`
+ SELECT wt.*, u.firstname, u.lastname, u.username, u.personalnummer, u.wochenstunden, u.urlaubstage, u.overtime_offset_hours, u.vacation_offset_days, u.arbeitstage,
+ dl.firstname as downloaded_by_firstname,
+ dl.lastname as downloaded_by_lastname,
+ (SELECT COUNT(*) FROM weekly_timesheets wt2
+ WHERE wt2.user_id = wt.user_id
+ AND wt2.week_start = wt.week_start
+ AND wt2.week_end = wt.week_end) as total_versions
+ FROM weekly_timesheets wt
+ JOIN users u ON wt.user_id = u.id
+ LEFT JOIN users dl ON wt.pdf_downloaded_by = dl.id
+ WHERE wt.status = 'eingereicht'
+ AND wt.user_id = ?
+ ORDER BY wt.week_start DESC, wt.user_id, wt.version DESC
+ `, [userId], (err, timesheets) => {
+ if (err) {
+ console.error('Fehler beim Laden der Stundenzettel (Verwaltung-API /employee/:id/weeks):', err);
+ return res.status(500).send('Fehler beim Laden der Verwaltungsdaten.');
+ }
+
+ if (!timesheets || timesheets.length === 0) {
+ // Kein Inhalt, aber 200, damit das Frontend eine leere Anzeige darstellen kann
+ return res.send('');
+ }
+
+ // Gruppierung wie in /verwaltung, aber nur für einen Mitarbeiter
+ const groupedByEmployee = {};
+
+ timesheets.forEach(ts => {
+ const uid = ts.user_id;
+ const weekKey = `${ts.week_start}_${ts.week_end}`;
+
+ if (!groupedByEmployee[uid]) {
+ groupedByEmployee[uid] = {
+ user: {
+ id: ts.user_id,
+ firstname: ts.firstname,
+ lastname: ts.lastname,
+ username: ts.username,
+ personalnummer: ts.personalnummer,
+ wochenstunden: ts.wochenstunden,
+ urlaubstage: ts.urlaubstage,
+ overtime_offset_hours: ts.overtime_offset_hours,
+ vacation_offset_days: ts.vacation_offset_days
+ },
+ weeks: {}
+ };
+ }
+
+ if (!groupedByEmployee[uid].weeks[weekKey]) {
+ groupedByEmployee[uid].weeks[weekKey] = {
+ week_start: ts.week_start,
+ week_end: ts.week_end,
+ total_versions: ts.total_versions,
+ versions: []
+ };
+ }
+
+ groupedByEmployee[uid].weeks[weekKey].versions.push(ts);
+ });
+
+ const employee = Object.values(groupedByEmployee)[0];
+ if (!employee) {
+ return res.send('');
+ }
+
+ // Prüfe für jede Woche, ob nach dem letzten Download eine neue Version eingereicht wurde
+ Object.values(employee.weeks).forEach(week => {
+ let lastDownloadTime = null;
+ week.versions.forEach(version => {
+ if (version.pdf_downloaded_at) {
+ const downloadTime = new Date(version.pdf_downloaded_at).getTime();
+ if (!lastDownloadTime || downloadTime > lastDownloadTime) {
+ lastDownloadTime = downloadTime;
+ }
+ }
+ });
+
+ let hasNewVersionAfterDownload = false;
+ if (lastDownloadTime) {
+ week.versions.forEach(version => {
+ if (version.submitted_at) {
+ const submittedTime = new Date(version.submitted_at).getTime();
+ if (submittedTime > lastDownloadTime) {
+ hasNewVersionAfterDownload = true;
+ }
+ }
+ });
+ }
+
+ week.has_new_version_after_download = hasNewVersionAfterDownload;
+ });
+
+ const weeks = Object.values(employee.weeks).sort((a, b) => {
+ return new Date(b.week_start) - new Date(a.week_start);
+ });
+
+ const viewUser = {
+ firstname: req.session.firstname,
+ lastname: req.session.lastname
+ };
+
+ res.render('verwaltung-weeks-partial', {
+ employee: { user: employee.user, weeks },
+ user: viewUser
+ });
+ });
+ });
+
// Plausibilitätsprüfung Projektnummer: 7 Ziffern, beginnt mit 5, dann YY, dann 4 Ziffern
function isValidProjectNumber(value) {
if (!value || String(value).trim() === '') return false;
diff --git a/views/verwaltung-weeks-partial.ejs b/views/verwaltung-weeks-partial.ejs
new file mode 100644
index 0000000..da0502f
--- /dev/null
+++ b/views/verwaltung-weeks-partial.ejs
@@ -0,0 +1,146 @@
+<% employee.weeks.forEach(function(week, weekIndex) { %>
+
+
+
+
+
+
+
+ | Version |
+ Eingereicht am |
+ Grund |
+ Kommentar |
+ Aktionen |
+
+
+
+ <% week.versions.forEach(function(ts) { %>
+
+ |
+
+ Version <%= ts.version || 1 %>
+
+ <% if (ts.pdf_downloaded_at) { %>
+ <%
+ let downloadedByName = 'Unbekannt';
+ if (ts.downloaded_by_firstname && ts.downloaded_by_lastname) {
+ downloadedByName = `${ts.downloaded_by_firstname} ${ts.downloaded_by_lastname}`;
+ } else if (ts.downloaded_by_firstname) {
+ downloadedByName = ts.downloaded_by_firstname;
+ } else if (ts.downloaded_by_lastname) {
+ downloadedByName = ts.downloaded_by_lastname;
+ }
+ %>
+
+ ✓ Heruntergeladen von <%= downloadedByName %>
+
+ <% } else { %>
+ ⭕ Nicht heruntergeladen
+ <% } %>
+ |
+ <%= new Date(ts.submitted_at).toLocaleString('de-DE') %> |
+
+ <% if (ts.version_reason && ts.version_reason.trim() !== '') { %>
+
+ <%= ts.version_reason.length > 50 ? ts.version_reason.substring(0, 50) + '...' : ts.version_reason %>
+
+ <% } else { %>
+ -
+ <% } %>
+ |
+
+
+ |
+
+
+
+ 📥 PDF herunterladen
+
+ |
+
+
+ |
+
+ |
+
+ <% }); %>
+
+
+
+
+<% }); %>
+
diff --git a/views/verwaltung.ejs b/views/verwaltung.ejs
index 6703961..94c4da7 100644
--- a/views/verwaltung.ejs
+++ b/views/verwaltung.ejs
@@ -152,12 +152,17 @@
- Kalenderwochen: <%= employee.weeks.length %>
+ Kalenderwochen: <%= employee.weekCount %>
Krankheitstage (<%= new Date().getFullYear() %>):
-
+ <% if (employee.latest_week_start && employee.latest_week_end) { %>
+
+ <% } %>