Verwaltung: Backend auf Lazyloading der Kalenderwochen umgestellt

This commit is contained in:
2026-03-12 18:24:37 +01:00
parent 8595b099db
commit 91603f1617
3 changed files with 544 additions and 380 deletions

View File

@@ -152,12 +152,17 @@
</button>
</div>
<div style="display: inline-block; margin-right: 20px;">
<strong>Kalenderwochen:</strong> <span><%= employee.weeks.length %></span>
<strong>Kalenderwochen:</strong> <span><%= employee.weekCount %></span>
</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>
<% if (employee.latest_week_start && employee.latest_week_end) { %>
<div class="group-stats group-stats-header" data-user-id="<%= employee.user.id %>" data-week-start="<%= employee.latest_week_start %>" data-week-end="<%= employee.latest_week_end %>" style="display: none;">
<div class="stats-loading" style="display: inline-block; color: #666;">Lade Statistiken...</div>
</div>
<% } %>
</div>
</div>
<button class="btn btn-secondary btn-sm toggle-employee-btn" data-employee-index="<%= employeeIndex %>">
@@ -166,155 +171,7 @@
</div>
<!-- Level 2: Kalenderwochen -->
<div class="weeks-container" data-employee-index="<%= employeeIndex %>" style="display: none;">
<% employee.weeks.forEach(function(week, weekIndex) { %>
<div class="week-group" data-employee-index="<%= employeeIndex %>" data-week-index="<%= weekIndex %>">
<div class="week-header">
<div class="week-info">
<div class="week-dates">
<%
// Kalenderwoche berechnen
function getCalendarWeek(dateStr) {
const date = new Date(dateStr);
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
return weekNo;
}
const calendarWeek = getCalendarWeek(week.week_start);
%>
<strong>Kalenderwoche <%= String(calendarWeek).padStart(2, '0') %>:</strong> <%= new Date(week.week_start).toLocaleDateString('de-DE') %> -
<%= new Date(week.week_end).toLocaleDateString('de-DE') %>
</div>
<div class="group-stats" data-user-id="<%= employee.user.id %>" data-week-start="<%= week.week_start %>" data-week-end="<%= week.week_end %>" style="margin-top: 10px;">
<div class="stats-loading" style="display: inline-block; color: #666;">Lade Statistiken...</div>
</div>
<div class="week-versions-info" style="margin-top: 5px;">
<span class="version-count"><%= week.total_versions %> Version<%= week.total_versions !== 1 ? 'en' : '' %></span>
</div>
<% if (week.has_new_version_after_download) { %>
<div class="new-version-warning" style="margin-top: 10px;">
<strong>ACHTUNG: Neue version eingereicht</strong>
</div>
<% } %>
</div>
<button class="btn btn-secondary btn-sm toggle-versions-btn" data-employee-index="<%= employeeIndex %>" data-week-index="<%= weekIndex %>">
<span class="toggle-icon">▼</span> Versionen
</button>
</div>
<!-- Level 3: Versionen -->
<div class="versions-container" data-employee-index="<%= employeeIndex %>" data-week-index="<%= weekIndex %>" style="display: none;">
<table class="timesheet-table versions-table">
<thead>
<tr>
<th>Version</th>
<th>Eingereicht am</th>
<th>Grund</th>
<th>Kommentar</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% week.versions.forEach(function(ts) { %>
<tr class="timesheet-row" data-timesheet-id="<%= ts.id %>">
<td>
<span class="version-badge">
Version <%= ts.version || 1 %>
</span>
<% if (ts.pdf_downloaded_at) { %>
<%
let downloadedByName = 'Unbekannt';
if (ts.downloaded_by_firstname && ts.downloaded_by_lastname) {
downloadedByName = `${ts.downloaded_by_firstname} ${ts.downloaded_by_lastname}`;
} else if (ts.downloaded_by_firstname) {
downloadedByName = ts.downloaded_by_firstname;
} else if (ts.downloaded_by_lastname) {
downloadedByName = ts.downloaded_by_lastname;
}
%>
<span class="pdf-downloaded-marker" title="PDF wurde am <%= new Date(ts.pdf_downloaded_at).toLocaleString('de-DE') %> von <%= downloadedByName %> heruntergeladen">
✓ Heruntergeladen von <%= downloadedByName %>
</span>
<% } else { %>
<span class="pdf-not-downloaded-marker">⭕ Nicht heruntergeladen</span>
<% } %>
</td>
<td><%= new Date(ts.submitted_at).toLocaleString('de-DE') %></td>
<td>
<% if (ts.version_reason && ts.version_reason.trim() !== '') { %>
<span style="color: #666; font-size: 13px;" title="<%= ts.version_reason %>">
<%= ts.version_reason.length > 50 ? ts.version_reason.substring(0, 50) + '...' : ts.version_reason %>
</span>
<% } else { %>
<span style="color: #999; font-style: italic;">-</span>
<% } %>
</td>
<td>
<div class="admin-comment-cell" style="display: flex; gap: 8px; align-items: flex-start; min-width: 300px;">
<textarea
class="admin-comment-input"
data-timesheet-id="<%= ts.id %>"
rows="2"
style="flex: 1; min-width: 250px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; resize: vertical;"
placeholder="Kommentar..."><%= ts.admin_comment || '' %></textarea>
<button
class="btn btn-success btn-sm save-comment-btn"
data-timesheet-id="<%= ts.id %>"
style="white-space: nowrap; padding: 8px 12px;"
title="Kommentar speichern">
💾
</button>
</div>
</td>
<td>
<button class="btn btn-info btn-sm toggle-pdf-btn" data-timesheet-id="<%= ts.id %>">
<span class="arrow-icon">▶</span> PDF anzeigen
</button>
<a href="/api/timesheet/pdf/<%= ts.id %>" class="btn btn-primary btn-sm pdf-download-link" data-timesheet-id="<%= ts.id %>" target="_blank" download>
📥 PDF herunterladen
</a>
</td>
</tr>
<tr class="pdf-preview-row" data-timesheet-id="<%= ts.id %>" style="display: none;">
<td colspan="5">
<div class="pdf-preview-container">
<div class="pdf-preview-header">
<h3>PDF-Vorschau: <%= employee.user.firstname %> <%= employee.user.lastname %> - Version <%= ts.version || 1 %> - <%= new Date(ts.week_start).toLocaleDateString('de-DE') %> bis <%= new Date(ts.week_end).toLocaleDateString('de-DE') %></h3>
<button class="btn btn-secondary btn-sm close-pdf-btn" data-timesheet-id="<%= ts.id %>">
✕ Schließen
</button>
</div>
<div class="pdf-viewer-wrapper">
<iframe
data-src="/api/timesheet/pdf/<%= ts.id %>?inline=true"
class="pdf-iframe"
frameborder="0"
type="application/pdf"
allow="fullscreen">
<p>Ihr Browser unterstützt keine PDF-Vorschau.
<a href="/api/timesheet/pdf/<%= ts.id %>?inline=true" target="_blank">PDF in neuem Tab öffnen</a>
</p>
</iframe>
<div class="pdf-fallback">
<p>PDF wird geladen...</p>
<a href="/api/timesheet/pdf/<%= ts.id %>?inline=true" target="_blank" class="btn btn-primary">
PDF in neuem Tab öffnen
</a>
</div>
</div>
</div>
</td>
</tr>
<% }); %>
</tbody>
</table>
</div>
</div>
<% }); %>
</div>
<div class="weeks-container" data-employee-index="<%= employeeIndex %>" data-user-id="<%= employee.user.id %>" style="display: none;" data-loaded="false"></div>
</div>
<% }); %>
</div>
@@ -390,8 +247,227 @@
});
}
// Statistiken für alle Wochen initial laden
document.querySelectorAll('.group-stats').forEach(statsDiv => loadStatsForDiv(statsDiv));
function initWeeksInContainer(root) {
if (!root) return;
// Statistiken für alle Wochen/Container laden (Header + nachgeladene Wochen)
root.querySelectorAll('.group-stats').forEach(statsDiv => {
if (!statsDiv.dataset.statsInitialized) {
statsDiv.dataset.statsInitialized = 'true';
loadStatsForDiv(statsDiv);
}
});
// Versionen-Gruppen auf-/zuklappen (innerhalb einer Kalenderwoche)
root.querySelectorAll('.toggle-versions-btn').forEach(btn => {
if (btn.dataset.initialized === 'true') return;
btn.dataset.initialized = 'true';
btn.addEventListener('click', function() {
const weekGroup = this.closest('.week-group');
if (!weekGroup) return;
const versionsContainer = weekGroup.querySelector('.versions-container');
const toggleIcon = this.querySelector('.toggle-icon');
if (versionsContainer) {
if (versionsContainer.style.display === 'none' || !versionsContainer.style.display) {
versionsContainer.style.display = 'block';
toggleIcon.textContent = '▲';
this.classList.add('active');
} else {
versionsContainer.style.display = 'none';
toggleIcon.textContent = '▼';
this.classList.remove('active');
}
}
});
});
// PDF-Download Marker aktualisieren
root.querySelectorAll('.pdf-download-link').forEach(link => {
if (link.dataset.initialized === 'true') return;
link.dataset.initialized = 'true';
link.addEventListener('click', function() {
const timesheetId = this.dataset.timesheetId;
const currentUser = '<%= user.firstname %> <%= user.lastname %>';
// Nach kurzer Verzögerung Marker aktualisieren (wenn Download erfolgreich war)
setTimeout(() => {
fetch(`/api/timesheet/download-info/${timesheetId}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.downloaded) {
const marker = document.querySelector(`.timesheet-row[data-timesheet-id="${timesheetId}"] .pdf-not-downloaded-marker`);
if (marker) {
const downloadedBy = (data.downloaded_by_firstname && data.downloaded_by_lastname)
? `${data.downloaded_by_firstname} ${data.downloaded_by_lastname}`
: currentUser || 'Unbekannt';
const downloadedAt = data.downloaded_at
? new Date(data.downloaded_at).toLocaleString('de-DE')
: 'Gerade';
marker.outerHTML = `<span class="pdf-downloaded-marker" title="PDF wurde am ${downloadedAt} von ${downloadedBy} heruntergeladen">✓ Heruntergeladen von ${downloadedBy}</span>`;
}
}
})
.catch(err => {
console.error('Fehler beim Laden der Download-Info:', err);
const marker = document.querySelector(`.timesheet-row[data-timesheet-id="${timesheetId}"] .pdf-not-downloaded-marker`);
if (marker && currentUser) {
marker.outerHTML = `<span class="pdf-downloaded-marker" title="PDF wurde von ${currentUser} heruntergeladen">✓ Heruntergeladen von ${currentUser}</span>`;
}
});
}, 1500);
});
});
// PDF-Vorschau ein-/ausblenden (PDF erst bei Klick laden)
root.querySelectorAll('.toggle-pdf-btn').forEach(btn => {
if (btn.dataset.initialized === 'true') return;
btn.dataset.initialized = 'true';
btn.addEventListener('click', function() {
const timesheetId = this.dataset.timesheetId;
const previewRow = root.querySelector(`.pdf-preview-row[data-timesheet-id="${timesheetId}"]`);
const arrowIcon = this.querySelector('.arrow-icon');
const iframe = previewRow ? previewRow.querySelector('.pdf-iframe') : null;
if (previewRow && (previewRow.style.display === 'none' || !previewRow.style.display)) {
// Alle anderen PDF-Vorschauen schließen (nur innerhalb root relevant)
root.querySelectorAll('.pdf-preview-row').forEach(row => {
if (row.dataset.timesheetId !== timesheetId) {
row.style.display = 'none';
const otherBtn = root.querySelector(`.toggle-pdf-btn[data-timesheet-id="${row.dataset.timesheetId}"]`);
if (otherBtn) {
const otherIcon = otherBtn.querySelector('.arrow-icon');
if (otherIcon) otherIcon.textContent = '▶';
otherBtn.classList.remove('active');
}
}
});
previewRow.style.display = 'table-row';
arrowIcon.textContent = '▼';
this.classList.add('active');
if (iframe) {
const currentSrc = iframe.getAttribute('src');
if (!currentSrc) {
const dataSrc = iframe.getAttribute('data-src') || `/api/timesheet/pdf/${timesheetId}?inline=true`;
iframe.setAttribute('src', dataSrc);
}
}
setTimeout(() => {
previewRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
} else {
if (previewRow) {
previewRow.style.display = 'none';
}
arrowIcon.textContent = '▶';
this.classList.remove('active');
}
});
});
// Schließen-Button für PDF-Vorschau
root.querySelectorAll('.close-pdf-btn').forEach(btn => {
if (btn.dataset.initialized === 'true') return;
btn.dataset.initialized = 'true';
btn.addEventListener('click', function() {
const timesheetId = this.dataset.timesheetId;
const previewRow = root.querySelector(`.pdf-preview-row[data-timesheet-id="${timesheetId}"]`);
const toggleBtn = root.querySelector(`.toggle-pdf-btn[data-timesheet-id="${timesheetId}"]`);
if (previewRow) previewRow.style.display = 'none';
if (toggleBtn) {
const icon = toggleBtn.querySelector('.arrow-icon');
if (icon) icon.textContent = '▶';
toggleBtn.classList.remove('active');
}
});
});
// Kommentar speichern
root.querySelectorAll('.save-comment-btn').forEach(btn => {
if (btn.dataset.initialized === 'true') return;
btn.dataset.initialized = 'true';
btn.addEventListener('click', async function() {
const timesheetId = this.dataset.timesheetId;
const commentInput = root.querySelector(`.admin-comment-input[data-timesheet-id="${timesheetId}"]`);
if (!commentInput) {
console.error('Kommentar-Input nicht gefunden');
return;
}
const comment = commentInput.value.trim();
const originalButtonText = this.innerHTML;
this.disabled = true;
this.innerHTML = '...';
this.title = 'Speichere...';
try {
const response = await fetch(`/api/verwaltung/timesheet/${timesheetId}/comment`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ comment: comment })
});
const result = await response.json();
if (result.success) {
this.innerHTML = '✓';
this.title = 'Gespeichert!';
this.style.backgroundColor = '#28a745';
setTimeout(() => {
this.innerHTML = originalButtonText;
this.title = 'Kommentar speichern';
this.style.backgroundColor = '';
this.disabled = false;
}, 2000);
} else {
alert('Fehler beim Speichern: ' + (result.error || 'Unbekannter Fehler'));
this.innerHTML = originalButtonText;
this.title = 'Kommentar speichern';
this.disabled = false;
}
} catch (error) {
console.error('Fehler beim Speichern des Kommentars:', error);
alert('Fehler beim Speichern des Kommentars');
this.innerHTML = originalButtonText;
this.title = 'Kommentar speichern';
this.disabled = false;
}
});
});
// Kommentar auch per Enter-Taste speichern (Strg+Enter)
root.querySelectorAll('.admin-comment-input').forEach(input => {
if (input.dataset.initialized === 'true') return;
input.dataset.initialized = 'true';
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
const timesheetId = this.dataset.timesheetId;
const saveBtn = root.querySelector(`.save-comment-btn[data-timesheet-id="${timesheetId}"]`);
if (saveBtn) {
saveBtn.click();
}
}
});
});
}
// Header-Statistiken (verbleibender Urlaub / Basiswerte) initial laden
initWeeksInContainer(document);
// Krankheitstage für alle Mitarbeiter laden
async function loadSickDays() {
@@ -853,224 +929,49 @@
});
});
// Mitarbeiter-Gruppen auf-/zuklappen (zeigt/versteckt Wochen)
// Mitarbeiter-Gruppen auf-/zuklappen (zeigt/versteckt Wochen, lazy load)
document.querySelectorAll('.toggle-employee-btn').forEach(btn => {
btn.addEventListener('click', function() {
const employeeIndex = this.dataset.employeeIndex;
const weeksContainer = document.querySelector(`.weeks-container[data-employee-index="${employeeIndex}"]`);
const toggleIcon = this.querySelector('.toggle-icon');
if (weeksContainer) {
if (weeksContainer.style.display === 'none' || !weeksContainer.style.display) {
weeksContainer.style.display = 'block';
toggleIcon.textContent = '▲';
this.classList.add('active');
} else {
weeksContainer.style.display = 'none';
toggleIcon.textContent = '▼';
this.classList.remove('active');
}
}
});
});
// Versionen-Gruppen auf-/zuklappen (innerhalb einer Kalenderwoche)
document.querySelectorAll('.toggle-versions-btn').forEach(btn => {
btn.addEventListener('click', function() {
const employeeIndex = this.dataset.employeeIndex;
const weekIndex = this.dataset.weekIndex;
const versionsContainer = document.querySelector(`.versions-container[data-employee-index="${employeeIndex}"][data-week-index="${weekIndex}"]`);
const toggleIcon = this.querySelector('.toggle-icon');
if (versionsContainer) {
if (versionsContainer.style.display === 'none' || !versionsContainer.style.display) {
versionsContainer.style.display = 'block';
toggleIcon.textContent = '▲';
this.classList.add('active');
} else {
versionsContainer.style.display = 'none';
toggleIcon.textContent = '▼';
this.classList.remove('active');
}
}
});
});
// PDF-Download Marker aktualisieren
document.querySelectorAll('.pdf-download-link').forEach(link => {
link.addEventListener('click', function() {
const timesheetId = this.dataset.timesheetId;
const currentUser = '<%= user.firstname %> <%= user.lastname %>';
// Nach kurzer Verzögerung Marker aktualisieren (wenn Download erfolgreich war)
// Lade aktualisierte Daten vom Server
setTimeout(() => {
fetch(`/api/timesheet/download-info/${timesheetId}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.downloaded) {
const marker = document.querySelector(`.timesheet-row[data-timesheet-id="${timesheetId}"] .pdf-not-downloaded-marker`);
if (marker) {
// Verwende Server-Daten, oder Fallback auf aktuellen User
const downloadedBy = (data.downloaded_by_firstname && data.downloaded_by_lastname)
? `${data.downloaded_by_firstname} ${data.downloaded_by_lastname}`
: currentUser || 'Unbekannt';
const downloadedAt = data.downloaded_at
? new Date(data.downloaded_at).toLocaleString('de-DE')
: 'Gerade';
marker.outerHTML = `<span class="pdf-downloaded-marker" title="PDF wurde am ${downloadedAt} von ${downloadedBy} heruntergeladen">✓ Heruntergeladen von ${downloadedBy}</span>`;
}
}
})
.catch(err => {
console.error('Fehler beim Laden der Download-Info:', err);
// Fallback: Verwende aktuellen User
const marker = document.querySelector(`.timesheet-row[data-timesheet-id="${timesheetId}"] .pdf-not-downloaded-marker`);
if (marker && currentUser) {
marker.outerHTML = `<span class="pdf-downloaded-marker" title="PDF wurde von ${currentUser} heruntergeladen">✓ Heruntergeladen von ${currentUser}</span>`;
}
});
}, 1500);
});
});
// PDF-Vorschau ein-/ausblenden (PDF erst bei Klick laden)
document.querySelectorAll('.toggle-pdf-btn').forEach(btn => {
btn.addEventListener('click', function() {
const timesheetId = this.dataset.timesheetId;
const previewRow = document.querySelector(`.pdf-preview-row[data-timesheet-id="${timesheetId}"]`);
const arrowIcon = this.querySelector('.arrow-icon');
const iframe = previewRow ? previewRow.querySelector('.pdf-iframe') : null;
if (previewRow && (previewRow.style.display === 'none' || !previewRow.style.display)) {
// Alle anderen PDF-Vorschauen schließen
document.querySelectorAll('.pdf-preview-row').forEach(row => {
if (row.dataset.timesheetId !== timesheetId) {
row.style.display = 'none';
const otherBtn = document.querySelector(`.toggle-pdf-btn[data-timesheet-id="${row.dataset.timesheetId}"]`);
if (otherBtn) {
otherBtn.querySelector('.arrow-icon').textContent = '▶';
otherBtn.classList.remove('active');
}
}
});
// Diese PDF-Vorschau öffnen
previewRow.style.display = 'table-row';
arrowIcon.textContent = '▼';
this.classList.add('active');
// iframe-src erst jetzt setzen (lazy load), falls noch nicht gesetzt
if (iframe) {
const currentSrc = iframe.getAttribute('src');
if (!currentSrc) {
const dataSrc = iframe.getAttribute('data-src') || `/api/timesheet/pdf/${timesheetId}?inline=true`;
iframe.setAttribute('src', dataSrc);
}
}
// Scroll zur PDF-Vorschau
setTimeout(() => {
previewRow.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
} else {
// PDF-Vorschau schließen
if (previewRow) {
previewRow.style.display = 'none';
}
arrowIcon.textContent = '▶';
this.classList.remove('active');
}
});
});
// Schließen-Button
document.querySelectorAll('.close-pdf-btn').forEach(btn => {
btn.addEventListener('click', function() {
const timesheetId = this.dataset.timesheetId;
const previewRow = document.querySelector(`.pdf-preview-row[data-timesheet-id="${timesheetId}"]`);
const toggleBtn = document.querySelector(`.toggle-pdf-btn[data-timesheet-id="${timesheetId}"]`);
previewRow.style.display = 'none';
if (toggleBtn) {
toggleBtn.querySelector('.arrow-icon').textContent = '▶';
toggleBtn.classList.remove('active');
}
});
});
// Kommentar speichern
document.querySelectorAll('.save-comment-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const timesheetId = this.dataset.timesheetId;
const commentInput = document.querySelector(`.admin-comment-input[data-timesheet-id="${timesheetId}"]`);
if (!commentInput) {
console.error('Kommentar-Input nicht gefunden');
const employeeGroup = this.closest('.employee-group');
if (!employeeGroup) return;
const weeksContainer = employeeGroup.querySelector('.weeks-container');
const toggleIcon = this.querySelector('.toggle-icon');
const userId = employeeGroup.dataset.employeeId;
if (!weeksContainer) return;
// Wochen bei erstem Öffnen per AJAX nachladen
if (weeksContainer.dataset.loaded !== 'true' && userId) {
weeksContainer.style.display = 'block';
weeksContainer.innerHTML = '<div class="weeks-loading" style="padding: 10px; color: #666;">Lade Kalenderwochen...</div>';
toggleIcon.textContent = '▲';
this.classList.add('active');
try {
const response = await fetch(`/api/verwaltung/employee/${userId}/weeks`);
if (!response.ok) {
throw new Error('Fehler beim Laden der Wochen');
}
const html = await response.text();
weeksContainer.innerHTML = html || '<div style="padding: 10px; color: #666;">Keine eingereichten Wochen vorhanden.</div>';
weeksContainer.dataset.loaded = 'true';
initWeeksInContainer(weeksContainer);
} catch (error) {
console.error('Fehler beim Nachladen der Wochen:', error);
weeksContainer.innerHTML = '<div class="weeks-error" style="padding: 10px; color: #dc3545;">Fehler beim Laden der Wochen. Bitte erneut versuchen.</div>';
}
return;
}
const comment = commentInput.value.trim();
const originalButtonText = this.innerHTML;
// Button deaktivieren während des Speicherns
this.disabled = true;
this.innerHTML = '...';
this.title = 'Speichere...';
try {
const response = await fetch(`/api/verwaltung/timesheet/${timesheetId}/comment`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ comment: comment })
});
const result = await response.json();
if (result.success) {
// Erfolgs-Feedback
this.innerHTML = '✓';
this.title = 'Gespeichert!';
this.style.backgroundColor = '#28a745';
// Nach 2 Sekunden zurücksetzen
setTimeout(() => {
this.innerHTML = originalButtonText;
this.title = 'Kommentar speichern';
this.style.backgroundColor = '';
}, 2000);
} else {
alert('Fehler beim Speichern: ' + (result.error || 'Unbekannter Fehler'));
this.innerHTML = originalButtonText;
this.title = 'Kommentar speichern';
this.disabled = false;
}
} catch (error) {
console.error('Fehler beim Speichern des Kommentars:', error);
alert('Fehler beim Speichern des Kommentars');
this.innerHTML = originalButtonText;
this.title = 'Kommentar speichern';
this.disabled = false;
}
});
});
// Kommentar auch per Enter-Taste speichern (Strg+Enter)
document.querySelectorAll('.admin-comment-input').forEach(input => {
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
const timesheetId = this.dataset.timesheetId;
const saveBtn = document.querySelector(`.save-comment-btn[data-timesheet-id="${timesheetId}"]`);
if (saveBtn) {
saveBtn.click();
}
// Nur Anzeige toggeln, wenn bereits geladen
if (weeksContainer.style.display === 'none' || !weeksContainer.style.display) {
weeksContainer.style.display = 'block';
toggleIcon.textContent = '▲';
this.classList.add('active');
} else {
weeksContainer.style.display = 'none';
toggleIcon.textContent = '▼';
this.classList.remove('active');
}
});
});