PDF View i Adminbereich

This commit is contained in:
2026-03-18 17:24:13 +01:00
parent a92694f693
commit 8152aab15b
4 changed files with 599 additions and 0 deletions

View File

@@ -304,8 +304,257 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}
// PDF-Archiv (Timesheets)
const pdfYearSelect = document.getElementById('pdfYearSelect');
const pdfUserSelect = document.getElementById('pdfUserSelect');
const pdfFilesContainer = document.getElementById('pdfFilesContainer');
const pdfRefreshBtn = document.getElementById('pdfRefreshBtn');
if (pdfYearSelect && pdfUserSelect && pdfFilesContainer) {
if (pdfRefreshBtn) {
pdfRefreshBtn.addEventListener('click', function() {
clearPdfPreview();
loadPdfYears();
});
}
pdfYearSelect.addEventListener('change', function() {
clearPdfPreview();
const year = this.value;
if (year) loadPdfUsers(year);
});
pdfUserSelect.addEventListener('change', function() {
clearPdfPreview();
const year = pdfYearSelect.value;
const userId = this.value;
if (year && userId) loadPdfFiles(year, userId);
});
loadPdfYears();
}
});
function setPdfStatus(text, color) {
const el = document.getElementById('pdfArchiveStatus');
if (!el) return;
el.textContent = text || '';
el.style.color = color || '';
}
function clearPdfPreview() {
const container = document.getElementById('pdfPreviewContainer');
const iframe = document.getElementById('pdfPreviewIframe');
if (container) container.style.display = 'none';
if (iframe) iframe.src = '';
}
function formatBytes(bytes) {
if (bytes === null || bytes === undefined || !Number.isFinite(Number(bytes))) return '-';
const num = Number(bytes);
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let i = 0;
let n = num;
while (n >= 1024 && i < units.length - 1) {
n /= 1024;
i += 1;
}
const fixed = i === 0 ? 0 : 1;
return `${n.toFixed(fixed)} ${units[i]}`;
}
async function loadPdfYears() {
const pdfYearSelect = document.getElementById('pdfYearSelect');
const pdfUserSelect = document.getElementById('pdfUserSelect');
const pdfFilesContainer = document.getElementById('pdfFilesContainer');
if (!pdfYearSelect || !pdfUserSelect || !pdfFilesContainer) return;
setPdfStatus('Lade PDF-Jahre...', 'blue');
pdfYearSelect.innerHTML = '<option value="">-- Laden... --</option>';
pdfUserSelect.innerHTML = '';
pdfUserSelect.disabled = true;
pdfFilesContainer.innerHTML = '';
clearPdfPreview();
try {
const response = await fetch('/admin/api/pdfs/years');
const result = await response.json();
if (!response.ok) {
throw new Error(result && result.error ? result.error : 'Fehler beim Laden der PDF-Jahre');
}
const years = Array.isArray(result.years) ? result.years : [];
if (years.length === 0) {
pdfYearSelect.innerHTML = '<option value="">Keine PDFs gefunden</option>';
setPdfStatus('Keine PDF-Dateien im Archiv gefunden.', '');
return;
}
pdfYearSelect.innerHTML = '';
years.forEach(year => {
const opt = document.createElement('option');
opt.value = year;
opt.textContent = year;
pdfYearSelect.appendChild(opt);
});
const firstYear = years[0];
pdfYearSelect.value = firstYear;
setPdfStatus('Jahre geladen. Lade User...', '');
await loadPdfUsers(firstYear);
} catch (error) {
console.error('Fehler beim Laden der PDF-Jahre:', error);
setPdfStatus('Fehler beim Laden der PDF-Jahre', 'red');
pdfYearSelect.innerHTML = '<option value="">Fehler</option>';
pdfUserSelect.disabled = true;
}
}
async function loadPdfUsers(year) {
const pdfYearSelect = document.getElementById('pdfYearSelect');
const pdfUserSelect = document.getElementById('pdfUserSelect');
const pdfFilesContainer = document.getElementById('pdfFilesContainer');
if (!pdfYearSelect || !pdfUserSelect || !pdfFilesContainer) return;
setPdfStatus(`Lade PDFs für ${year}...`, 'blue');
pdfUserSelect.disabled = true;
pdfUserSelect.innerHTML = '<option value="">-- Laden... --</option>';
pdfFilesContainer.innerHTML = '';
clearPdfPreview();
try {
const response = await fetch(`/admin/api/pdfs/users?year=${encodeURIComponent(year)}`);
const result = await response.json();
if (!response.ok) {
throw new Error(result && result.error ? result.error : 'Fehler beim Laden der User-Ordner');
}
const users = Array.isArray(result.users)
? result.users
: (Array.isArray(result.userIds) ? result.userIds.map(id => ({ id, name: id })) : []);
const userIds = users.map(u => u.id);
if (users.length === 0) {
pdfUserSelect.innerHTML = '<option value="">Keine PDFs</option>';
pdfUserSelect.disabled = true;
setPdfStatus(`Keine PDF-Dateien für das Jahr ${year} gefunden.`, '');
return;
}
pdfUserSelect.disabled = false;
pdfUserSelect.innerHTML = '';
users.forEach(user => {
const userId = user.id;
const opt = document.createElement('option');
opt.value = userId;
opt.textContent = user.name || userId;
pdfUserSelect.appendChild(opt);
});
const firstUserId = userIds[0];
pdfUserSelect.value = firstUserId;
setPdfStatus('User geladen. Lade Dateien...', '');
await loadPdfFiles(year, firstUserId);
} catch (error) {
console.error('Fehler beim Laden der PDF-User:', error);
setPdfStatus('Fehler beim Laden der PDF-User', 'red');
pdfUserSelect.innerHTML = '<option value="">Fehler</option>';
pdfUserSelect.disabled = true;
}
}
async function loadPdfFiles(year, userId) {
const pdfFilesContainer = document.getElementById('pdfFilesContainer');
if (!pdfFilesContainer) return;
setPdfStatus('Lade PDF-Dateien...', 'blue');
pdfFilesContainer.innerHTML = '';
clearPdfPreview();
try {
const response = await fetch(`/admin/api/pdfs/files?year=${encodeURIComponent(year)}&userId=${encodeURIComponent(userId)}`);
const result = await response.json();
if (!response.ok) {
throw new Error(result && result.error ? result.error : 'Fehler beim Laden der PDF-Dateien');
}
const files = Array.isArray(result.files) ? result.files : [];
if (files.length === 0) {
pdfFilesContainer.innerHTML = '<p style="color:#666;">Keine PDF-Dateien gefunden.</p>';
setPdfStatus(`Keine PDFs für User ${userId} (${year}).`, '');
return;
}
const rowsHtml = files
.map(file => {
const mtimeLabel = file.mtime ? new Date(file.mtime).toLocaleString('de-DE') : '-';
const sizeLabel = formatBytes(file.size);
// name ist serverseitig streng gefiltert; hier daher direkt in JS-String verwenden.
return `
<tr>
<td style="padding:4px; word-break:break-all;">
<code>${file.name}</code>
</td>
<td style="padding:4px;">${sizeLabel}</td>
<td style="padding:4px;">${mtimeLabel}</td>
<td style="padding:4px; white-space:nowrap;">
<button type="button" class="btn btn-primary btn-sm" onclick="previewPdf('${year}','${userId}','${file.name}')">Vorschau</button>
<button type="button" class="btn btn-secondary btn-sm" style="margin-left:6px;" onclick="downloadPdf('${year}','${userId}','${file.name}')">Download</button>
</td>
</tr>
`;
})
.join('');
pdfFilesContainer.innerHTML = `
<table style="width: 100%; min-width: 980px; border-collapse: collapse;">
<thead>
<tr>
<th style="text-align:left; padding:4px;">Datei</th>
<th style="text-align:left; padding:4px;">Größe</th>
<th style="text-align:left; padding:4px;">Änderung</th>
<th style="text-align:left; padding:4px;">Aktionen</th>
</tr>
</thead>
<tbody>
${rowsHtml}
</tbody>
</table>
`;
setPdfStatus(`${files.length} PDF(s) geladen.`, 'green');
} catch (error) {
console.error('Fehler beim Laden der PDF-Dateien:', error);
pdfFilesContainer.innerHTML = '<p style="color: red;">Fehler beim Laden der PDF-Dateien.</p>';
setPdfStatus('Fehler beim Laden der PDF-Dateien', 'red');
}
}
function previewPdf(year, userId, name) {
const iframe = document.getElementById('pdfPreviewIframe');
const container = document.getElementById('pdfPreviewContainer');
if (!iframe || !container) return;
setPdfStatus(`Vorschau: ${name}`, 'blue');
container.style.display = 'block';
iframe.src = `/admin/api/pdfs/file?year=${encodeURIComponent(year)}&userId=${encodeURIComponent(userId)}&name=${encodeURIComponent(name)}&inline=true`;
}
function downloadPdf(year, userId, name) {
const url = `/admin/api/pdfs/file?year=${encodeURIComponent(year)}&userId=${encodeURIComponent(userId)}&name=${encodeURIComponent(name)}&inline=false`;
setPdfStatus('Download gestartet...', '');
const link = document.createElement('a');
link.href = url;
link.target = '_blank';
link.rel = 'noopener';
link.click();
}
async function loadTimesheetDuplicates() {
const container = document.getElementById('timesheetDuplicatesContainer');
if (!container) return;