UI redesign step 2: Übersicht in Stitch style
- Page header gains the headline / subtitle pair and a primary "Neue Schaltung" CTA on the right that jumps back to the wizard. - Filter bar moves into a bordered white card with label-caps labels, compact 32px inputs, JetBrains Mono numeric inputs for ID/phone/date and a "Zurücksetzen" pill on the right. - Data table uses the Stitch shell: sticky surface-container-high header row with label-caps column titles, 40px body rows, zebra striping on the even rows and a hover highlight. The previously separate Eingabe/Schaltungs-Datum columns are merged into a single "Daten" cell (E:/A:), Anbieter renders as a primary pill, and the Bearbeiten/Löschen/Dokumente buttons become icon-only actions. - updateSchaltungenTable() now delegates to a single renderer used by both the initial load and the client-side filter, so the layout stays consistent after applying filters. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a1cab7ea6d
commit
0912a6812f
2 files changed
+117
-110
No files matched your search
+59
-42
@@ -1166,67 +1166,84 @@
|
||||
|
||||
<!-- Übersicht Tab -->
|
||||
<div id="overview" class="tab-content">
|
||||
<h2>Schaltungs-Übersicht</h2>
|
||||
<div class="flex flex-col gap-lg">
|
||||
|
||||
<!-- Filter für Übersicht -->
|
||||
<div class="filter-section" style="margin-bottom: 20px; padding: 20px; background: #f8f9fa; border-radius: 8px;">
|
||||
<h3 style="margin-bottom: 15px; color: #2c3e50;">Filter</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(6, 1fr) auto; gap: 15px; align-items: end;">
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="filterAnbieterOverview">Anbieter</label>
|
||||
<select id="filterAnbieterOverview">
|
||||
<!-- Page header -->
|
||||
<div class="flex justify-between items-end flex-wrap gap-md">
|
||||
<div>
|
||||
<h1 class="text-headline-md text-on-background mb-xs">Schaltungs-Übersicht</h1>
|
||||
<p class="text-body-sm text-on-surface-variant">Verwaltung und Kontrolle aller erfassten Schaltungen.</p>
|
||||
</div>
|
||||
<button type="button" onclick="showTab('schaltung')" class="bg-primary-container text-on-primary hover:bg-primary transition-colors rounded-lg px-md py-sm text-body-sm flex items-center gap-sm shadow-sm">
|
||||
<span class="material-symbols-outlined text-[18px]">add</span>
|
||||
Neue Schaltung
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter bar -->
|
||||
<div class="bg-surface-container-lowest border border-outline-variant rounded-lg p-md shadow-[0_2px_4px_rgba(0,0,0,0.04)] flex flex-wrap items-end gap-md">
|
||||
<div class="flex flex-col gap-xs flex-grow min-w-[150px]">
|
||||
<label class="text-label-caps text-on-surface-variant uppercase" for="filterAnbieterOverview">Anbieter</label>
|
||||
<select id="filterAnbieterOverview" class="w-full bg-surface-container-lowest border border-outline-variant rounded h-8 px-sm text-body-sm text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary">
|
||||
<option value="">Alle Anbieter</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="filterTarifOverview">Tarif</label>
|
||||
<select id="filterTarifOverview">
|
||||
<div class="flex flex-col gap-xs flex-grow min-w-[150px]">
|
||||
<label class="text-label-caps text-on-surface-variant uppercase" for="filterTarifOverview">Tarif</label>
|
||||
<select id="filterTarifOverview" class="w-full bg-surface-container-lowest border border-outline-variant rounded h-8 px-sm text-body-sm text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary">
|
||||
<option value="">Alle Tarife</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="filterSchaltungsIdOverview">Schaltungs-ID</label>
|
||||
<input type="text" id="filterSchaltungsIdOverview" placeholder="ID1, ID2, ID3... (kommagetrennt)">
|
||||
<div class="flex flex-col gap-xs flex-grow min-w-[150px]">
|
||||
<label class="text-label-caps text-on-surface-variant uppercase" for="filterSchaltungsIdOverview">Schaltungs-ID</label>
|
||||
<input type="text" id="filterSchaltungsIdOverview" placeholder="ID1, ID2…"
|
||||
class="w-full bg-surface-container-lowest border border-outline-variant rounded h-8 px-sm font-label-numeric text-label-numeric text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="filterRufnummerOverview">Rufnummer</label>
|
||||
<input type="text" id="filterRufnummerOverview" placeholder="Rufnummer suchen...">
|
||||
<div class="flex flex-col gap-xs flex-grow min-w-[150px]">
|
||||
<label class="text-label-caps text-on-surface-variant uppercase" for="filterRufnummerOverview">Rufnummer</label>
|
||||
<input type="text" id="filterRufnummerOverview" placeholder="Suche…"
|
||||
class="w-full bg-surface-container-lowest border border-outline-variant rounded h-8 px-sm font-label-numeric text-label-numeric text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="filterDatumVonOverview">Datum von</label>
|
||||
<input type="date" id="filterDatumVonOverview">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="filterDatumBisOverview">Datum bis</label>
|
||||
<input type="date" id="filterDatumBisOverview">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" onclick="clearFiltersOverview()" class="btn-secondary" style="padding: 12px 20px;">Zurücksetzen</button>
|
||||
<div class="flex flex-col gap-xs w-[240px]">
|
||||
<label class="text-label-caps text-on-surface-variant uppercase">Zeitraum</label>
|
||||
<div class="flex items-center gap-sm">
|
||||
<input type="date" id="filterDatumVonOverview"
|
||||
class="w-full bg-surface-container-lowest border border-outline-variant rounded h-8 px-sm font-label-numeric text-[12px] text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary">
|
||||
<span class="text-outline-variant">–</span>
|
||||
<input type="date" id="filterDatumBisOverview"
|
||||
class="w-full bg-surface-container-lowest border border-outline-variant rounded h-8 px-sm font-label-numeric text-[12px] text-on-surface focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary">
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onclick="clearFiltersOverview()"
|
||||
class="bg-surface-container-high text-on-surface border border-outline-variant hover:bg-surface-variant transition-colors rounded px-md h-8 text-body-sm flex items-center gap-xs ml-auto">
|
||||
<span class="material-symbols-outlined text-[16px]">restart_alt</span>
|
||||
Zurücksetzen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table id="schaltungenTable">
|
||||
<thead>
|
||||
<!-- Data table -->
|
||||
<div class="bg-surface-container-lowest border border-outline-variant rounded-lg shadow-[0_2px_4px_rgba(0,0,0,0.04)] overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table id="schaltungenTable" class="w-full text-left border-collapse">
|
||||
<thead class="bg-surface-container-high border-b-2 border-outline-variant">
|
||||
<tr>
|
||||
<th>Schaltungs-ID</th>
|
||||
<th>Eingabe-Datum</th>
|
||||
<th>Schaltungs-Datum</th>
|
||||
<th>Name</th>
|
||||
<th>Firma</th>
|
||||
<th>Rufnummer</th>
|
||||
<th>Anbieter</th>
|
||||
<th>Tarif</th>
|
||||
<th>Hardware</th>
|
||||
<th>Dokumente</th>
|
||||
<th>Aktionen</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase text-right w-[80px]">ID</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Daten</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Kunde / Firma</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Rufnummer</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Anbieter</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Tarif</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase">Hardware</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase text-center w-[80px]">Dokumente</th>
|
||||
<th class="px-md py-sm text-label-caps text-on-surface-variant uppercase text-center w-[80px]">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tbody class="text-body-sm text-on-surface"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Provisionen Tab -->
|
||||
<div id="provisionen" class="tab-content">
|
||||
|
||||
+58
-68
@@ -703,30 +703,64 @@ async function refreshDataAfterSchaltungChange() {
|
||||
async function loadSchaltungen() {
|
||||
try {
|
||||
const schaltungen = await apiRequest('/api/schaltungen');
|
||||
renderSchaltungenRows(schaltungen);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Schaltungen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Render schaltungen rows in the Stitch overview table.
|
||||
function renderSchaltungenRows(schaltungen) {
|
||||
const tbody = document.querySelector('#schaltungenTable tbody');
|
||||
|
||||
if (!tbody) return;
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (!schaltungen || !schaltungen.length) {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = '<td colspan="9" class="px-md py-lg text-center text-body-sm text-on-surface-variant">Keine Schaltungen vorhanden</td>';
|
||||
tbody.appendChild(tr);
|
||||
return;
|
||||
}
|
||||
schaltungen.forEach(s => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${escapeHtml(s.schaltungs_id)}</td>
|
||||
<td>${escapeHtml(formatDate(s.eingabe_datum))}</td>
|
||||
<td>${escapeHtml(formatDate(s.schaltungs_datum))}</td>
|
||||
<td>${escapeHtml(s.vorname)} ${escapeHtml(s.nachname)}</td>
|
||||
<td>${escapeHtml(s.firma || '-')}</td>
|
||||
<td>${escapeHtml(s.rufnummer)}</td>
|
||||
<td>${escapeHtml(s.anbieter_name)}</td>
|
||||
<td>${escapeHtml(s.tarif_name)}</td>
|
||||
<td>${escapeHtml(s.hardware || '')}</td>
|
||||
<td><button data-action="documents" data-id="${s.id}" class="btn-success">Dokumente</button></td>
|
||||
<td>
|
||||
<button data-action="edit" data-id="${s.id}" class="btn-success" style="margin-right: 5px;">Bearbeiten</button>
|
||||
<button data-action="delete" data-id="${s.id}" data-schaltungs-id="${escapeHtml(s.schaltungs_id)}" data-name="${escapeHtml(s.vorname + ' ' + s.nachname)}" class="btn-danger">Löschen</button>
|
||||
const tr = document.createElement('tr');
|
||||
tr.className = 'border-b border-outline-variant/30 even:bg-surface-container-low/50 hover:bg-surface-variant/30 transition-colors';
|
||||
tr.innerHTML = `
|
||||
<td class="px-md py-xs font-label-numeric text-label-numeric text-right text-on-surface-variant">${escapeHtml(s.schaltungs_id)}</td>
|
||||
<td class="px-md py-xs">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="font-label-numeric text-[11px] text-on-surface-variant">E: ${escapeHtml(formatDate(s.eingabe_datum))}</span>
|
||||
<span class="font-label-numeric text-[11px] text-primary">A: ${escapeHtml(formatDate(s.schaltungs_datum))}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-md py-xs">
|
||||
<div class="font-semibold text-on-background">${escapeHtml((s.vorname || '') + ' ' + (s.nachname || ''))}</div>
|
||||
<div class="text-[12px] text-on-surface-variant">${escapeHtml(s.firma || '–')}</div>
|
||||
</td>
|
||||
<td class="px-md py-xs font-label-numeric text-label-numeric text-on-surface-variant">${escapeHtml(s.rufnummer || '–')}</td>
|
||||
<td class="px-md py-xs">
|
||||
<span class="bg-primary/10 text-primary border border-primary/20 px-2 py-0.5 rounded-full text-label-caps">${escapeHtml(s.anbieter_name)}</span>
|
||||
</td>
|
||||
<td class="px-md py-xs">${escapeHtml(s.tarif_name)}</td>
|
||||
<td class="px-md py-xs text-on-surface-variant">${escapeHtml(s.hardware || '–')}</td>
|
||||
<td class="px-md py-xs text-center">
|
||||
<button data-action="documents" data-id="${s.id}" title="Dokumente anzeigen"
|
||||
class="bg-tertiary/10 text-tertiary hover:bg-tertiary/20 transition-colors rounded p-1 inline-flex items-center justify-center border border-tertiary/20">
|
||||
<span class="material-symbols-outlined text-[16px]">description</span>
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-md py-xs">
|
||||
<div class="flex items-center justify-center gap-sm">
|
||||
<button data-action="edit" data-id="${s.id}" title="Bearbeiten"
|
||||
class="text-outline hover:text-primary transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">edit</span>
|
||||
</button>
|
||||
<button data-action="delete" data-id="${s.id}" data-schaltungs-id="${escapeHtml(s.schaltungs_id)}" data-name="${escapeHtml((s.vorname || '') + ' ' + (s.nachname || ''))}" title="Löschen"
|
||||
class="text-outline hover:text-error transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
// Wire delegated handlers so we don't need inline onclick (avoids JS-injection via names).
|
||||
row.querySelectorAll('button[data-action]').forEach(btn => {
|
||||
tr.querySelectorAll('button[data-action]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const action = btn.dataset.action;
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
@@ -735,12 +769,8 @@ async function loadSchaltungen() {
|
||||
else if (action === 'delete') deleteSchaltung(id, btn.dataset.schaltungsId, btn.dataset.name);
|
||||
});
|
||||
});
|
||||
tbody.appendChild(row);
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Schaltungen:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Format any ISO-like date string (YYYY-MM-DD or full timestamp) as DD.MM.YYYY
|
||||
@@ -2256,52 +2286,12 @@ function applyFiltersProvisionen() {
|
||||
function updateSchaltungenTable(schaltungen) {
|
||||
const tbody = document.querySelector('#schaltungenTable tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (schaltungen.length === 0) {
|
||||
// Show "no results" message
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td colspan="11" style="text-align: center; padding: 40px; color: #7f8c8d; font-style: italic; font-size: 16px;">
|
||||
📋 Keine Schaltungen entsprechen den gewählten Filterkriterien
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
if (!schaltungen || schaltungen.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="px-md py-lg text-center text-body-sm text-on-surface-variant">Keine Schaltungen entsprechen den Filterkriterien</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
schaltungen.forEach(s => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${escapeHtml(s.schaltungs_id)}</td>
|
||||
<td>${escapeHtml(formatDate(s.eingabe_datum))}</td>
|
||||
<td>${escapeHtml(formatDate(s.schaltungs_datum))}</td>
|
||||
<td>${escapeHtml(s.vorname)} ${escapeHtml(s.nachname)}</td>
|
||||
<td>${escapeHtml(s.firma || '-')}</td>
|
||||
<td>${escapeHtml(s.rufnummer)}</td>
|
||||
<td>${escapeHtml(s.anbieter_name)}</td>
|
||||
<td>${escapeHtml(s.tarif_name)}</td>
|
||||
<td>${escapeHtml(s.hardware || '')}</td>
|
||||
<td>
|
||||
<button data-filt-action="documents" data-id="${s.id}" class="btn-success">Dokumente</button>
|
||||
</td>
|
||||
<td>
|
||||
<button data-filt-action="edit" data-id="${s.id}" class="btn-secondary">Bearbeiten</button>
|
||||
<button data-filt-action="delete" data-id="${s.id}" data-schaltungs-id="${escapeHtml(s.schaltungs_id)}" data-name="${escapeHtml(s.vorname + ' ' + s.nachname)}" class="btn-danger">Löschen</button>
|
||||
</td>
|
||||
`;
|
||||
row.querySelectorAll('button[data-filt-action]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const action = btn.dataset.filtAction;
|
||||
const id = parseInt(btn.dataset.id, 10);
|
||||
if (action === 'documents') showDocuments(id);
|
||||
else if (action === 'edit') editSchaltung(id);
|
||||
else if (action === 'delete') deleteSchaltung(id, btn.dataset.schaltungsId, btn.dataset.name);
|
||||
});
|
||||
});
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
// Delegate to the canonical renderer used by loadSchaltungen.
|
||||
renderSchaltungenRows(schaltungen);
|
||||
}
|
||||
|
||||
// Update Provisionen table with filtered data
|
||||
|
||||
Reference in new issue
Block a user