Übersicht polish: filter widths, transparent table buttons, Stitch docs modal

Three follow-ups to the previous overview redesign:

- Filter bar: replace flex-grow inputs with fixed widths so the
  Zurücksetzen button no longer ends up overlapping the second date
  field. The date range gets its own 320px slot wide enough for two
  flexible date pickers and the separator.
- Table icon buttons used to inherit the legacy global
  `button { background, padding, color }` rule and rendered as solid
  blue blocks. Add a `table button { background: transparent; … }`
  reset; class-level utilities still win for buttons that opt back in
  with bg-primary etc., so the "Neue Schaltung" CTA above the table
  keeps its colour.
- Documents modal rewritten in Stitch style: clean white card with a
  light header and footer separator, file rows as bordered cards with
  a Material icon, primary "Dokumente hinzufügen" pill and outline
  "Schließen". showDocuments / closeDocumentsModal toggle a Tailwind
  hidden/flex pair instead of the legacy `.show` class. The matching
  legacy CSS blocks (.documents-modal*, .document-info, .download-btn,
  .view-btn, .no-documents*) are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kerimatilganandClaude Opus 4.7 committed 2026-05-15 18:06:15 +02:00
1 parent 0912a6812f
commit 40bd1b8009
2 files changed
+88 -234

No files matched your search

+40 -49
View File
@@ -790,65 +790,55 @@ async function showDocuments(schaltungId) {
const documents = await apiRequest(`/api/schaltungen/${schaltungId}/dokumente`);
const modal = document.getElementById('documentsModal');
const modalBody = document.getElementById('documentsModalBody');
modalBody.innerHTML = '';
if (documents.length === 0) {
modalBody.innerHTML = `
<div class="no-documents">
<div class="no-documents-icon">📄</div>
<div>Keine Dokumente vorhanden</div>
<div style="font-size: 14px; margin-top: 10px;">Für diese Schaltung wurden noch keine Dokumente hochgeladen.</div>
<div style="margin-top: 15px;">
<button class="btn btn-primary" onclick="document.getElementById('additionalDocumentUpload').click()"
style="background: #007bff; border-color: #007bff;">
📎 Erste Dokumente hinzufügen
</button>
</div>
</div>
const empty = document.createElement('div');
empty.className = 'flex flex-col items-center justify-center py-xl text-center gap-sm';
empty.innerHTML = `
<span class="material-symbols-outlined text-[40px] text-outline">draft</span>
<div class="text-body-md text-on-surface">Keine Dokumente vorhanden</div>
<div class="text-body-sm text-on-surface-variant">Für diese Schaltung wurden noch keine Dokumente hochgeladen.</div>
`;
modalBody.appendChild(empty);
} else {
documents.forEach(doc => {
const docItem = document.createElement('div');
docItem.className = 'document-item';
// Get file extension for icon
const fileExt = doc.original_filename.split('.').pop().toLowerCase();
let icon = '📄';
if (['pdf'].includes(fileExt)) icon = '📋';
else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) icon = '🖼️';
else if (['doc', 'docx'].includes(fileExt)) icon = '📝';
// Format file size
let iconName = 'description';
if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) iconName = 'image';
else if (['doc', 'docx'].includes(fileExt)) iconName = 'article';
const fileSize = doc.filesize ? `${(doc.filesize / 1024).toFixed(1)} KB` : 'Unbekannt';
// Format upload date
const uploadDate = doc.uploaded_at ? formatDate(doc.uploaded_at) : 'Unbekannt';
const showView = fileExt === 'pdf';
const safeName = escapeHtml(doc.original_filename);
docItem.innerHTML = `
<div class="document-info">
<div class="document-icon">${icon}</div>
<div class="document-details">
<div class="document-name">${safeName}</div>
<div class="document-meta">Größe: ${escapeHtml(fileSize)} • Hochgeladen: ${escapeHtml(uploadDate)}</div>
</div>
const card = document.createElement('div');
card.className = 'flex items-center gap-md p-md border border-outline-variant rounded-lg bg-surface-container-lowest hover:bg-surface-container-low transition-colors';
card.innerHTML = `
<span class="material-symbols-outlined text-primary text-[28px] shrink-0">${iconName}</span>
<div class="flex flex-col min-w-0 flex-1">
<span class="text-body-md text-on-surface truncate" title="${safeName}">${safeName}</span>
<span class="text-body-sm text-on-surface-variant">${escapeHtml(fileSize)} · Hochgeladen: ${escapeHtml(uploadDate)}</span>
</div>
<div style="display: flex; gap: 8px;">
${showView ? `<button class="view-btn" data-doc-action="view" data-id="${doc.id}" data-name="${safeName}">👁️ Anzeigen</button>` : ''}
<button class="download-btn" data-doc-action="download" data-id="${doc.id}">
📥 Download
<div class="flex items-center gap-xs shrink-0">
${showView ? `<button data-doc-action="view" data-id="${doc.id}" data-name="${safeName}" title="Anzeigen"
class="text-outline hover:text-primary transition-colors p-1">
<span class="material-symbols-outlined text-[20px]">visibility</span>
</button>` : ''}
<button data-doc-action="download" data-id="${doc.id}" title="Download"
class="text-outline hover:text-primary transition-colors p-1">
<span class="material-symbols-outlined text-[20px]">download</span>
</button>
<button class="btn-danger" data-doc-action="delete" data-id="${doc.id}" data-name="${safeName}" data-schaltung-id="${schaltungId}"
style="background: #dc3545; border-color: #dc3545; color: white; padding: 8px 12px; border-radius: 4px; font-size: 12px;"
title="Dokument löschen">
🗑️
<button data-doc-action="delete" data-id="${doc.id}" data-name="${safeName}" data-schaltung-id="${schaltungId}" title="Löschen"
class="text-outline hover:text-error transition-colors p-1">
<span class="material-symbols-outlined text-[20px]">delete</span>
</button>
</div>
`;
docItem.querySelectorAll('button[data-doc-action]').forEach(btn => {
card.querySelectorAll('button[data-doc-action]').forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.dataset.docAction;
const id = parseInt(btn.dataset.id, 10);
@@ -857,18 +847,18 @@ async function showDocuments(schaltungId) {
else if (action === 'delete') deleteDocument(id, btn.dataset.name, parseInt(btn.dataset.schaltungId, 10));
});
});
modalBody.appendChild(docItem);
modalBody.appendChild(card);
});
}
// Setup file upload handler for additional documents
const fileInput = document.getElementById('additionalDocumentUpload');
fileInput.onchange = function(event) {
fileInput.onchange = function (event) {
uploadAdditionalDocuments(schaltungId, event.target.files);
};
modal.classList.add('show');
modal.classList.remove('hidden');
modal.classList.add('flex');
} catch (error) {
console.error('Fehler beim Laden der Dokumente:', error);
@@ -878,7 +868,8 @@ async function showDocuments(schaltungId) {
function closeDocumentsModal() {
const modal = document.getElementById('documentsModal');
modal.classList.remove('show');
modal.classList.add('hidden');
modal.classList.remove('flex');
}
function downloadDocument(documentId) {