Collabsable automations
This commit is contained in:
@@ -113,6 +113,29 @@ function registerVerwaltungRoutes(app) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// API: Krankheitstage für einen User im aktuellen Jahr abrufen
|
||||||
|
app.get('/api/verwaltung/user/:id/sick-days', requireVerwaltung, (req, res) => {
|
||||||
|
const userId = req.params.id;
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const yearStart = `${currentYear}-01-01`;
|
||||||
|
const yearEnd = `${currentYear}-12-31`;
|
||||||
|
|
||||||
|
db.all(`SELECT DISTINCT date
|
||||||
|
FROM timesheet_entries
|
||||||
|
WHERE user_id = ? AND date >= ? AND date <= ? AND sick_status = 1
|
||||||
|
ORDER BY date`,
|
||||||
|
[userId, yearStart, yearEnd],
|
||||||
|
(err, entries) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(500).json({ error: 'Fehler beim Abrufen der Krankheitstage' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sickDays = entries ? entries.length : 0;
|
||||||
|
res.json({ sickDays: sickDays, year: currentYear });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// API: Überstunden- und Urlaubsstatistiken für einen User abrufen
|
// API: Überstunden- und Urlaubsstatistiken für einen User abrufen
|
||||||
app.get('/api/verwaltung/user/:id/stats', requireVerwaltung, (req, res) => {
|
app.get('/api/verwaltung/user/:id/stats', requireVerwaltung, (req, res) => {
|
||||||
const userId = req.params.id;
|
const userId = req.params.id;
|
||||||
|
|||||||
@@ -73,35 +73,44 @@
|
|||||||
<div class="stat-unit">von <span id="totalVacation">-</span> Tagen</div>
|
<div class="stat-unit">von <span id="totalVacation">-</span> Tagen</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- URL-Erfassung -->
|
<!-- Zeiterfassung (URL & IP) -->
|
||||||
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
|
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
|
||||||
<h3 style="font-size: 14px; margin-bottom: 15px; color: #2c3e50;">Zeiterfassung per URL</h3>
|
<h3 style="font-size: 14px; margin-bottom: 0; color: #2c3e50; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px;" onclick="toggleTimeCapture()">
|
||||||
<div class="form-group" style="margin-bottom: 15px;">
|
<span class="toggle-icon-time-capture" style="display: inline-block; transition: transform 0.3s;">▶</span>
|
||||||
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-in URL</label>
|
Automatische Zeiterfassung
|
||||||
<div style="display: flex; gap: 5px;">
|
</h3>
|
||||||
<input type="text" id="checkinUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
|
<div id="timeCaptureContent" style="display: none; margin-top: 15px;">
|
||||||
<button onclick="copyToClipboard('checkinUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
|
<!-- URL-Erfassung -->
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<h4 style="font-size: 13px; margin-bottom: 10px; color: #555;">Zeiterfassung per URL</h4>
|
||||||
|
<div class="form-group" style="margin-bottom: 15px;">
|
||||||
|
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-in URL</label>
|
||||||
|
<div style="display: flex; gap: 5px;">
|
||||||
|
<input type="text" id="checkinUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
|
||||||
|
<button onclick="copyToClipboard('checkinUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom: 15px;">
|
||||||
|
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-out URL</label>
|
||||||
|
<div style="display: flex; gap: 5px;">
|
||||||
|
<input type="text" id="checkoutUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
|
||||||
|
<button onclick="copyToClipboard('checkoutUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="form-group" style="margin-bottom: 15px;">
|
|
||||||
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-out URL</label>
|
|
||||||
<div style="display: flex; gap: 5px;">
|
|
||||||
<input type="text" id="checkoutUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
|
|
||||||
<button onclick="copyToClipboard('checkoutUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- IP-Erfassung -->
|
<!-- IP-Erfassung -->
|
||||||
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #e0e0e0;">
|
<div style="padding-top: 15px; border-top: 1px solid #e0e0e0;">
|
||||||
<h3 style="font-size: 14px; margin-bottom: 15px; color: #2c3e50;">IP-basierte Zeiterfassung</h3>
|
<h4 style="font-size: 13px; margin-bottom: 10px; color: #555;">IP-basierte Zeiterfassung</h4>
|
||||||
<div class="form-group" style="margin-bottom: 15px;">
|
<div class="form-group" style="margin-bottom: 15px;">
|
||||||
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Ping-IP Adresse</label>
|
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Ping-IP Adresse</label>
|
||||||
<div style="display: flex; gap: 5px;">
|
<div style="display: flex; gap: 5px;">
|
||||||
<input type="text" id="pingIpInput" placeholder="z.B. 192.168.1.100" style="flex: 1; padding: 8px; font-size: 12px; border: 1px solid #ddd; border-radius: 4px;">
|
<input type="text" id="pingIpInput" placeholder="z.B. 192.168.1.100" style="flex: 1; padding: 8px; font-size: 12px; border: 1px solid #ddd; border-radius: 4px;">
|
||||||
<button onclick="window.savePingIP()" class="btn btn-sm btn-success" style="padding: 8px 12px;">Speichern</button>
|
<button onclick="window.savePingIP()" class="btn btn-sm btn-success" style="padding: 8px 12px;">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<p style="font-size: 11px; color: #666; margin-top: 5px; font-style: italic;">Ihre IP-Adresse für automatische Zeiterfassung</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="font-size: 11px; color: #666; margin-top: 5px; font-style: italic;">Ihre IP-Adresse für automatische Zeiterfassung</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,6 +135,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zeiterfassung ein-/ausklappen
|
||||||
|
function toggleTimeCapture() {
|
||||||
|
const content = document.getElementById('timeCaptureContent');
|
||||||
|
const icon = document.querySelector('.toggle-icon-time-capture');
|
||||||
|
|
||||||
|
if (content && icon) {
|
||||||
|
if (content.style.display === 'none') {
|
||||||
|
content.style.display = 'block';
|
||||||
|
icon.style.transform = 'rotate(90deg)';
|
||||||
|
} else {
|
||||||
|
content.style.display = 'none';
|
||||||
|
icon.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// URL-Kopier-Funktion
|
// URL-Kopier-Funktion
|
||||||
function copyToClipboard(inputId) {
|
function copyToClipboard(inputId) {
|
||||||
const input = document.getElementById(inputId);
|
const input = document.getElementById(inputId);
|
||||||
|
|||||||
@@ -113,6 +113,10 @@
|
|||||||
<div style="display: inline-block; margin-right: 20px;">
|
<div style="display: inline-block; margin-right: 20px;">
|
||||||
<strong>Kalenderwochen:</strong> <span><%= employee.weeks.length %></span>
|
<strong>Kalenderwochen:</strong> <span><%= employee.weeks.length %></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display: inline-block; margin-right: 20px;">
|
||||||
|
<strong>Krankheitstage (<span class="sick-days-year"><%= new Date().getFullYear() %></span>):</strong>
|
||||||
|
<span class="sick-days-count" data-user-id="<%= employee.user.id %>" style="color: #e74c3c;">-</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-secondary btn-sm toggle-employee-btn" data-employee-index="<%= employeeIndex %>">
|
<button class="btn btn-secondary btn-sm toggle-employee-btn" data-employee-index="<%= employeeIndex %>">
|
||||||
@@ -330,6 +334,36 @@
|
|||||||
// Statistiken für alle Wochen initial laden
|
// Statistiken für alle Wochen initial laden
|
||||||
document.querySelectorAll('.group-stats').forEach(statsDiv => loadStatsForDiv(statsDiv));
|
document.querySelectorAll('.group-stats').forEach(statsDiv => loadStatsForDiv(statsDiv));
|
||||||
|
|
||||||
|
// Krankheitstage für alle Mitarbeiter laden
|
||||||
|
async function loadSickDays() {
|
||||||
|
const sickDaysElements = document.querySelectorAll('.sick-days-count');
|
||||||
|
const userIds = Array.from(sickDaysElements).map(el => el.dataset.userId);
|
||||||
|
const uniqueUserIds = [...new Set(userIds)];
|
||||||
|
|
||||||
|
for (const userId of uniqueUserIds) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/verwaltung/user/${userId}/sick-days`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Fehler beim Laden der Krankheitstage');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Alle Elemente für diesen User aktualisieren
|
||||||
|
document.querySelectorAll(`.sick-days-count[data-user-id="${userId}"]`).forEach(el => {
|
||||||
|
el.textContent = data.sickDays || 0;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Krankheitstage für User', userId, ':', error);
|
||||||
|
document.querySelectorAll(`.sick-days-count[data-user-id="${userId}"]`).forEach(el => {
|
||||||
|
el.textContent = '-';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Krankheitstage beim Laden der Seite abrufen
|
||||||
|
loadSickDays();
|
||||||
|
|
||||||
// Überstunden-Offset speichern
|
// Überstunden-Offset speichern
|
||||||
document.querySelectorAll('.save-overtime-offset-btn').forEach(btn => {
|
document.querySelectorAll('.save-overtime-offset-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', async function() {
|
btn.addEventListener('click', async function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user