Aufräumen und header zentralisieren
This commit is contained in:
Binary file not shown.
@@ -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
|
||||||
@@ -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();
|
|
||||||
BIN
public/images/icons/icon-120x120.png
Normal file
BIN
public/images/icons/icon-120x120.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/images/icons/icon-152x152.png
Normal file
BIN
public/images/icons/icon-152x152.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/icons/icon-167x167.png
Normal file
BIN
public/images/icons/icon-167x167.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/icons/icon-180x180.png
Normal file
BIN
public/images/icons/icon-180x180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
public/images/icons/icon-192x192.png
Normal file
BIN
public/images/icons/icon-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/icons/icon-512x512.png
Normal file
BIN
public/images/icons/icon-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
@@ -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
20
public/manifest.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
17
views/header.ejs
Normal 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">
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user