282 lines
9.1 KiB
Plaintext
282 lines
9.1 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">
|
|
<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;
|
|
}
|
|
</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-secondary">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>
|
|
|
|
<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 remainingOvertime = totalOvertime - totalOvertimeTaken + (data.overtime_offset_hours || 0);
|
|
|
|
// Zusammenfassung anzeigen
|
|
document.getElementById('totalOvertime').textContent =
|
|
(totalOvertime >= 0 ? '+' : '') + totalOvertime.toFixed(2) + ' h';
|
|
document.getElementById('totalOvertimeTaken').textContent =
|
|
totalOvertimeTaken.toFixed(2) + ' h';
|
|
document.getElementById('remainingOvertime').textContent =
|
|
(remainingOvertime >= 0 ? '+' : '') + remainingOvertime.toFixed(2) + ' h';
|
|
document.getElementById('remainingOvertime').className =
|
|
'summary-value ' + (remainingOvertime >= 0 ? 'overtime-positive' : 'overtime-negative');
|
|
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>
|