Files
SDSStundenerfassung/views/overtime-breakdown.ejs

314 lines
10 KiB
Plaintext

<!DOCTYPE html>
<html lang="de-DE">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Language" content="de-DE">
<title>Überstunden-Auswertung - Stundenerfassung</title>
<link rel="icon" type="image/png" href="/images/favicon.png">
<link rel="stylesheet" href="/css/style.css">
<%- include('header') %>
<style>
.overtime-breakdown-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.page-title {
font-size: 28px;
color: #2c3e50;
margin: 0;
}
.overtime-table {
width: 100%;
border-collapse: collapse;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
}
.overtime-table thead {
background-color: #2c3e50;
color: white;
}
.overtime-table th {
padding: 15px;
text-align: left;
font-weight: 600;
}
.overtime-table td {
padding: 12px 15px;
border-bottom: 1px solid #e0e0e0;
}
.overtime-table tbody tr:hover {
background-color: #f8f9fa;
}
.overtime-table tbody tr:last-child td {
border-bottom: none;
}
.overtime-positive {
color: #27ae60;
font-weight: 600;
}
.overtime-negative {
color: #e74c3c;
font-weight: 600;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.no-data {
text-align: center;
padding: 40px;
color: #666;
}
.summary-box {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.summary-box h3 {
margin-top: 0;
margin-bottom: 15px;
color: #2c3e50;
}
.summary-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #e0e0e0;
}
.summary-item:last-child {
border-bottom: none;
}
.summary-label {
font-weight: 500;
color: #555;
}
.summary-value {
font-weight: 600;
color: #2c3e50;
}
.summary-value.overtime-positive {
color: #27ae60 !important;
}
.summary-value.overtime-negative {
color: #e74c3c !important;
}
</style>
</head>
<body>
<div class="navbar">
<div class="container">
<div class="navbar-brand">
<img src="/images/header.png" alt="Logo" class="navbar-logo">
<h1>Stundenerfassung</h1>
</div>
<div class="nav-right">
<span>Willkommen, <%= user.firstname %> <%= user.lastname %></span>
<% if (user.roles && user.roles.length > 1) { %>
<select id="roleSwitcher" class="role-switcher" style="margin-right: 10px; padding: 5px 10px; border-radius: 4px; border: 1px solid #ddd;">
<% const roleLabels = { 'mitarbeiter': 'Mitarbeiter', 'verwaltung': 'Verwaltung', 'admin': 'Administrator' }; %>
<% user.roles.forEach(function(role) { %>
<option value="<%= role %>" <%= user.currentRole === role ? 'selected' : '' %>><%= roleLabels[role] || role %></option>
<% }); %>
</select>
<% } %>
<a href="/logout" class="btn btn-logout">Abmelden</a>
</div>
</div>
</div>
<div class="overtime-breakdown-container">
<div class="page-header">
<h2 class="page-title">Überstunden-Auswertung</h2>
<a href="/dashboard" class="btn btn-secondary">Zurück zum Dashboard</a>
</div>
<div id="summaryBox" class="summary-box" style="display: none;">
<h3>Zusammenfassung</h3>
<div class="summary-item">
<span class="summary-label">Gesamt Überstunden:</span>
<span class="summary-value" id="totalOvertime">-</span>
</div>
<div class="summary-item">
<span class="summary-label">Davon genommen:</span>
<span class="summary-value" id="totalOvertimeTaken">-</span>
</div>
<div class="summary-item">
<span class="summary-label">Verbleibend:</span>
<span class="summary-value" id="remainingOvertime">-</span>
</div>
<div class="summary-item" id="offsetItem" style="display: none;">
<span class="summary-label">Manuelle Korrektur (Verwaltung):</span>
<span class="summary-value" id="overtimeOffset">-</span>
</div>
</div>
<div id="loading" class="loading">Lade Daten...</div>
<div id="noData" class="no-data" style="display: none;">
<p>Keine eingereichten Wochen gefunden.</p>
<p style="margin-top: 10px; font-size: 14px; color: #999;">Überstunden werden erst angezeigt, nachdem Wochen abgeschickt wurden.</p>
</div>
<table id="overtimeTable" class="overtime-table" style="display: none;">
<thead>
<tr>
<th>Kalenderwoche</th>
<th>Zeitraum</th>
<th>Gesamtstunden</th>
<th>Sollstunden</th>
<th>Überstunden</th>
<th>Genommen</th>
<th>Urlaubstage</th>
</tr>
</thead>
<tbody id="overtimeTableBody">
</tbody>
</table>
</div>
<script>
// Rollenwechsel
const roleSwitcher = document.getElementById('roleSwitcher');
if (roleSwitcher) {
roleSwitcher.addEventListener('change', async function() {
const role = this.value;
try {
const response = await fetch('/api/user/switch-role', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role })
});
const data = await response.json();
if (data.success) {
// Redirect basierend auf Rolle
if (role === 'admin') {
window.location.href = '/admin';
} else if (role === 'verwaltung') {
window.location.href = '/verwaltung';
} else {
window.location.href = '/dashboard';
}
}
} catch (error) {
console.error('Fehler beim Rollenwechsel:', error);
}
});
}
// Datum formatieren (DD.MM.YYYY)
function formatDate(dateStr) {
const date = new Date(dateStr);
return date.toLocaleDateString('de-DE');
}
// Überstunden-Daten laden
async function loadOvertimeBreakdown() {
const loadingEl = document.getElementById('loading');
const noDataEl = document.getElementById('noData');
const tableEl = document.getElementById('overtimeTable');
const tableBodyEl = document.getElementById('overtimeTableBody');
const summaryBoxEl = document.getElementById('summaryBox');
try {
const response = await fetch('/api/user/overtime-breakdown');
if (!response.ok) {
throw new Error('Fehler beim Laden der Daten');
}
const data = await response.json();
loadingEl.style.display = 'none';
if (!data.weeks || data.weeks.length === 0) {
noDataEl.style.display = 'block';
return;
}
// Zusammenfassung berechnen
let totalOvertime = 0;
let totalOvertimeTaken = 0;
data.weeks.forEach(week => {
totalOvertime += week.overtime_hours;
totalOvertimeTaken += week.overtime_taken;
});
const overtimeOffset = data.overtime_offset_hours || 0;
const remainingOvertime = totalOvertime - totalOvertimeTaken + overtimeOffset;
// Zusammenfassung anzeigen
const totalOvertimeEl = document.getElementById('totalOvertime');
totalOvertimeEl.textContent =
(totalOvertime >= 0 ? '+' : '') + totalOvertime.toFixed(2) + ' h';
totalOvertimeEl.className =
'summary-value ' + (totalOvertime >= 0 ? 'overtime-positive' : 'overtime-negative');
const totalOvertimeTakenEl = document.getElementById('totalOvertimeTaken');
totalOvertimeTakenEl.textContent =
totalOvertimeTaken.toFixed(2) + ' h';
totalOvertimeTakenEl.className = 'summary-value overtime-positive';
const remainingOvertimeEl = document.getElementById('remainingOvertime');
remainingOvertimeEl.textContent =
(remainingOvertime >= 0 ? '+' : '') + remainingOvertime.toFixed(2) + ' h';
remainingOvertimeEl.className =
'summary-value ' + (remainingOvertime >= 0 ? 'overtime-positive' : 'overtime-negative');
// Manuelle Korrektur anzeigen (nur wenn vorhanden)
const offsetItem = document.getElementById('offsetItem');
const offsetValue = document.getElementById('overtimeOffset');
if (overtimeOffset !== 0) {
offsetValue.textContent = (overtimeOffset >= 0 ? '+' : '') + overtimeOffset.toFixed(2) + ' h';
offsetValue.className = 'summary-value ' + (overtimeOffset >= 0 ? 'overtime-positive' : 'overtime-negative');
offsetItem.style.display = 'flex';
} else {
offsetItem.style.display = 'none';
}
summaryBoxEl.style.display = 'block';
// Tabelle füllen
tableBodyEl.innerHTML = '';
data.weeks.forEach(week => {
const row = document.createElement('tr');
const calendarWeekStr = String(week.calendar_week).padStart(2, '0');
const dateRange = formatDate(week.week_start) + ' - ' + formatDate(week.week_end);
const overtimeClass = week.overtime_hours >= 0 ? 'overtime-positive' : 'overtime-negative';
const overtimeSign = week.overtime_hours >= 0 ? '+' : '';
row.innerHTML = `
<td><strong>${week.year} KW${calendarWeekStr}</strong></td>
<td>${dateRange}</td>
<td>${week.total_hours.toFixed(2)} h</td>
<td>${week.soll_stunden.toFixed(2)} h</td>
<td class="${overtimeClass}">${overtimeSign}${week.overtime_hours.toFixed(2)} h</td>
<td>${week.overtime_taken.toFixed(2)} h</td>
<td>${week.vacation_days > 0 ? week.vacation_days.toFixed(1) : '-'}</td>
`;
tableBodyEl.appendChild(row);
});
tableEl.style.display = 'table';
} catch (error) {
console.error('Fehler beim Laden der Überstunden-Auswertung:', error);
loadingEl.textContent = 'Fehler beim Laden der Daten. Bitte versuchen Sie es später erneut.';
}
}
// Beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
loadOvertimeBreakdown();
});
</script>
</body>
</html>