Aufräumen und header zentralisieren

This commit is contained in:
2026-02-05 01:23:49 +01:00
parent 063fb68b12
commit 7d6951334f
22 changed files with 58 additions and 182 deletions

Binary file not shown.

View File

@@ -10,7 +10,7 @@
- Wenn bereits heruntergeladen wurde und neue version da ist Meldung an Verwaltung. -> DONE Muss getestet werden - Wenn bereits heruntergeladen wurde und neue version da ist Meldung an Verwaltung. -> DONE Muss getestet werden
- Wenn ganzer Tag Urlaub gesetzt wird steht erst 8h (Urlaub) und dann nur noch 8h - Wenn ganzer Tag Urlaub gesetzt wird steht erst 8h (Urlaub) und dann nur noch 8h
- Feiertage im PDF anzeigen -> DONE Testen noch nicht depoyed - Feiertage im PDF anzeigen -> DONE
- Oben wenn woche eingereicht anzeigen als hilfestellung -> DONE - Oben wenn woche eingereicht anzeigen als hilfestellung -> DONE
- Ausgefüllte Tage anhand der Tage pro woche gültig setzten -> DONE Testen - Ausgefüllte Tage anhand der Tage pro woche gültig setzten -> DONE Testen
- Überstunden müssen anhand der Tagesstunden auch auf gültig setzten (Tag ausgefüllt wenn weniger als 8h) -> DONE sollte passen - Überstunden müssen anhand der Tagesstunden auch auf gültig setzten (Tag ausgefüllt wenn weniger als 8h) -> DONE sollte passen

View File

@@ -1,177 +0,0 @@
#!/usr/bin/env node
/**
* Erzeugt aus DSGVO-Dokumentation.md eine PDF-Datei.
* Verwendung: node generate-dsgvo-pdf.js
*/
const PDFDocument = require('pdfkit');
const fs = require('fs');
const path = require('path');
const MARGIN = 50;
const PAGE_WIDTH = 595; // A4
const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
function stripBold(text) {
return text.replace(/\*\*([^*]+)\*\*/g, '$1');
}
function isTableRow(line) {
return /^\|.+\|$/.test(line.trim()) && !/^[\s|:-]+$/.test(line.replace(/\s/g, ''));
}
function parseTableRows(lines, startIndex) {
const rows = [];
let i = startIndex;
while (i < lines.length && isTableRow(lines[i])) {
const line = lines[i];
if (/^[\s|:-]+$/.test(line.replace(/\s/g, ''))) {
i++;
continue; // separator line
}
const cells = line.split('|').slice(1, -1).map(c => c.trim());
if (cells.some(c => c)) rows.push(cells);
i++;
}
return { rows, nextIndex: i };
}
function writeText(doc, text, options = {}) {
const opts = { width: CONTENT_WIDTH, ...options };
doc.text(text, opts);
}
function addParagraph(doc, line, fontSize = 10) {
doc.fontSize(fontSize).font('Helvetica');
const text = stripBold(line.trim());
if (!text) return;
writeText(doc, text);
doc.moveDown(0.5);
}
function addBullet(doc, line, fontSize = 10) {
doc.fontSize(fontSize).font('Helvetica');
const text = stripBold(line.replace(/^[-*]\s*/, '').trim());
if (!text) return;
doc.text('• ', { continued: true });
doc.text(text, { width: CONTENT_WIDTH - 20 });
doc.moveDown(0.4);
}
function addTable(doc, rows, fontSize = 9) {
if (rows.length === 0) return;
const colCount = rows[0].length;
const colWidth = CONTENT_WIDTH / colCount;
const rowHeight = fontSize * 1.4;
const startY = doc.y;
doc.fontSize(fontSize);
rows.forEach((row, rowIndex) => {
const isHeader = rowIndex === 0;
if (isHeader) doc.font('Helvetica-Bold');
if (doc.y > 750) {
doc.addPage();
doc.y = MARGIN;
}
let x = MARGIN;
row.forEach((cell, cellIndex) => {
doc.text(cell, x, doc.y, { width: colWidth - 4, align: 'left' });
x += colWidth;
});
doc.y += rowHeight;
if (isHeader) doc.font('Helvetica');
});
doc.moveDown(0.5);
}
function generateDSGVOPdf() {
const mdPath = path.join(__dirname, 'DSGVO-Dokumentation.md');
const outPath = path.join(__dirname, 'DSGVO-Dokumentation.pdf');
if (!fs.existsSync(mdPath)) {
console.error('Datei nicht gefunden: DSGVO-Dokumentation.md');
process.exit(1);
}
const content = fs.readFileSync(mdPath, 'utf8');
const lines = content.split(/\r?\n/);
const doc = new PDFDocument({ margin: MARGIN, size: 'A4' });
const stream = fs.createWriteStream(outPath);
doc.pipe(stream);
let i = 0;
while (i < lines.length) {
const line = lines[i];
const trimmed = line.trim();
// Neue Seite wenn nötig
if (doc.y > 750) doc.addPage();
if (trimmed.startsWith('# ')) {
doc.fontSize(22).font('Helvetica-Bold');
writeText(doc, trimmed.slice(2).trim(), { align: 'center' });
doc.moveDown(0.5);
doc.fontSize(12).text('Stundenerfassungssystem', { align: 'center' });
doc.moveDown(1);
doc.font('Helvetica');
i++;
continue;
}
if (trimmed.startsWith('## ')) {
doc.fontSize(16).font('Helvetica-Bold');
writeText(doc, trimmed.slice(3).trim());
doc.moveDown(0.5);
doc.font('Helvetica').fontSize(10);
i++;
continue;
}
if (trimmed.startsWith('### ')) {
doc.fontSize(13).font('Helvetica-Bold');
writeText(doc, trimmed.slice(4).trim());
doc.moveDown(0.4);
doc.font('Helvetica').fontSize(10);
i++;
continue;
}
if (trimmed === '---') {
doc.moveDown(0.5);
i++;
continue;
}
if (isTableRow(line)) {
const { rows, nextIndex } = parseTableRows(lines, i);
addTable(doc, rows);
i = nextIndex;
continue;
}
if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) {
addBullet(doc, trimmed);
i++;
continue;
}
if (trimmed) {
addParagraph(doc, trimmed);
} else {
doc.moveDown(0.3);
}
i++;
}
doc.end();
stream.on('finish', () => {
console.log('PDF erstellt: ' + outPath);
});
stream.on('error', (err) => {
console.error('Fehler beim Schreiben der PDF:', err);
process.exit(1);
});
}
generateDSGVOPdf();

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -675,7 +675,9 @@ function renderWeek() {
} }
// Sollstunden berechnen // Sollstunden berechnen
const sollStunden = (userWochenstunden / userArbeitstage) * workdays; // Die Sollstunden für eine Woche sind immer die Wochenstunden, unabhängig von den Arbeitstagen
// Die Arbeitstage pro Woche bestimmen nur die Stunden pro Tag (für Urlaub/Krank), nicht die Wochenstunden
const sollStunden = userWochenstunden || 0;
// Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit)
let vacationHours = 0; let vacationHours = 0;
@@ -720,12 +722,14 @@ function updateOvertimeDisplay() {
} }
// Sollstunden berechnen // Sollstunden berechnen
const sollStunden = (userWochenstunden / userArbeitstage) * workdays; // Die Sollstunden für eine Woche sind immer die Wochenstunden, unabhängig von den Arbeitstagen
// Die Arbeitstage pro Woche bestimmen nur die Stunden pro Tag (für Urlaub/Krank), nicht die Wochenstunden
const sollStunden = userWochenstunden || 0;
// Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung // Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung
let totalHours = 0; let totalHours = 0;
let vacationHours = 0; let vacationHours = 0;
const fullDayHours = userWochenstunden ? (userWochenstunden / 5) : 8; const fullDayHours = getFullDayHours(); // Verwende die Hilfsfunktion statt manueller Berechnung
let fullDayOvertimeDays = 0; // Anzahl Tage mit 8 Überstunden (wie im Backend) let fullDayOvertimeDays = 0; // Anzahl Tage mit 8 Überstunden (wie im Backend)
const startDateObj = new Date(startDate); const startDateObj = new Date(startDate);
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
@@ -929,6 +933,12 @@ function updateOvertimeDisplay() {
overtimeHoursSpan.textContent = `${sign}${overtimeHours.toFixed(2)} h`; overtimeHoursSpan.textContent = `${sign}${overtimeHours.toFixed(2)} h`;
overtimeHoursSpan.style.color = overtimeHours >= 0 ? '#27ae60' : '#e74c3c'; overtimeHoursSpan.style.color = overtimeHours >= 0 ? '#27ae60' : '#e74c3c';
} }
// Gesamtstunden-Anzeige aktualisieren
const totalHoursElement = document.getElementById('totalHours');
if (totalHoursElement) {
totalHoursElement.textContent = totalHoursWithVacation.toFixed(2) + ' h';
}
} }
// Überstunden-Änderung verarbeiten // Überstunden-Änderung verarbeiten

20
public/manifest.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "SDS Systemtechnik GmbH",
"short_name": "SDS",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0a5ea8",
"icons": [
{
"src": "/public/images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/public/images/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@@ -6,6 +6,7 @@
<title>Admin - Stundenerfassung</title> <title>Admin - Stundenerfassung</title>
<link rel="icon" type="image/png" href="/images/favicon.png"> <link rel="icon" type="image/png" href="/images/favicon.png">
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<%- include('header') %>
</head> </head>
<body> <body>
<div class="navbar"> <div class="navbar">

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %> - Stundenerfassung</title> <title><%= title %> - Stundenerfassung</title>
<%- include('header') %>
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } * { margin: 0; padding: 0; box-sizing: border-box; }
body { body {

View File

@@ -7,6 +7,7 @@
<title>Dashboard - Stundenerfassung</title> <title>Dashboard - Stundenerfassung</title>
<link rel="icon" type="image/png" href="/images/favicon.png"> <link rel="icon" type="image/png" href="/images/favicon.png">
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<%- include('header') %>
</head> </head>
<body> <body>
<div class="navbar"> <div class="navbar">
@@ -55,7 +56,7 @@
</div> </div>
<div class="actions"> <div class="actions">
<div style="display: flex; gap: 10px; align-items: center;"> <div style="display: flex; gap: 10px; align-items: center; justify-content: center;">
<button id="submitWeek" class="btn btn-success" onclick="window.submitWeekHandler(event)" disabled>Woche abschicken</button> <button id="submitWeek" class="btn btn-success" onclick="window.submitWeekHandler(event)" disabled>Woche abschicken</button>
<button id="viewPdfBtn" class="btn btn-info" disabled>PDF anzeigen</button> <button id="viewPdfBtn" class="btn btn-info" disabled>PDF anzeigen</button>
</div> </div>

17
views/header.ejs Normal file
View File

@@ -0,0 +1,17 @@
<!-- Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Apple Icons -->
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-120x120.png">
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png">
<link rel="apple-touch-icon" sizes="167x167" href="/icons/icon-167x167.png">
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-180x180.png">
<!-- Android / PWA -->
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/icons/icon-512x512.png">
<!-- iOS App Mode -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="SDS">

View File

@@ -6,6 +6,7 @@
<title>Login - Stundenerfassung</title> <title>Login - Stundenerfassung</title>
<link rel="icon" type="image/png" href="/images/favicon.png"> <link rel="icon" type="image/png" href="/images/favicon.png">
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<%- include('header') %>
</head> </head>
<body> <body>
<div class="login-container"> <div class="login-container">

View File

@@ -7,6 +7,7 @@
<title>Überstunden-Auswertung - Stundenerfassung</title> <title>Überstunden-Auswertung - Stundenerfassung</title>
<link rel="icon" type="image/png" href="/images/favicon.png"> <link rel="icon" type="image/png" href="/images/favicon.png">
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<%- include('header') %>
<style> <style>
.overtime-breakdown-container { .overtime-breakdown-container {
max-width: 1200px; max-width: 1200px;

View File

@@ -6,6 +6,7 @@
<title>Verwaltung - Stundenerfassung</title> <title>Verwaltung - Stundenerfassung</title>
<link rel="icon" type="image/png" href="/images/favicon.png"> <link rel="icon" type="image/png" href="/images/favicon.png">
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css">
<%- include('header') %>
</head> </head>
<body> <body>
<div class="navbar"> <div class="navbar">