From bcdba20e095d1f8fa31fffe0715820ee2e4a2d74 Mon Sep 17 00:00:00 2001 From: Ferdinand Date: Wed, 8 Apr 2026 13:22:48 +0200 Subject: [PATCH] feat: data tab with JSON load/save and history management --- js/history.js | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 js/history.js diff --git a/js/history.js b/js/history.js new file mode 100644 index 0000000..34e24e6 --- /dev/null +++ b/js/history.js @@ -0,0 +1,148 @@ +import { state } from './app.js'; +import { loadFromJSON, saveToJSON, removeHistoryEntry } from './data.js'; +import { downloadBlob } from './excel.js'; + +export function renderDataTab() { + const container = document.getElementById('tab-data'); + container.replaceChildren(); + + const title = document.createElement('h2'); + title.className = 'section-title'; + title.textContent = 'Daten'; + container.appendChild(title); + + const grid = document.createElement('div'); + grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:32px'; + + // Left: save/load + const saveLoadCol = document.createElement('div'); + + const saveTitle = document.createElement('h3'); + saveTitle.style.cssText = 'margin-bottom:12px;font-size:15px'; + saveTitle.textContent = 'Speichern / Laden'; + saveLoadCol.appendChild(saveTitle); + + const saveRow = document.createElement('div'); + saveRow.className = 'row'; + const saveBtn = document.createElement('button'); + saveBtn.type = 'button'; + saveBtn.className = 'primary'; + saveBtn.textContent = 'Daten speichern (.json)'; + saveRow.appendChild(saveBtn); + saveLoadCol.appendChild(saveRow); + + const loadRow = document.createElement('div'); + loadRow.className = 'row'; + const loadBtn = document.createElement('button'); + loadBtn.type = 'button'; + loadBtn.textContent = 'Daten laden (.json)'; + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json'; + fileInput.style.display = 'none'; + loadRow.append(loadBtn, fileInput); + saveLoadCol.appendChild(loadRow); + + const hint = document.createElement('p'); + hint.style.cssText = 'color:#6b7280;font-size:12px;margin-top:8px'; + hint.textContent = 'Speichert Mitarbeiterliste, Einschränkungen und Verlauf. Beim nächsten Mal diese Datei laden.'; + saveLoadCol.appendChild(hint); + + // Right: history + const historyCol = document.createElement('div'); + const historyTitle = document.createElement('h3'); + historyTitle.style.cssText = 'margin-bottom:12px;font-size:15px'; + historyTitle.textContent = 'Verlauf'; + historyCol.appendChild(historyTitle); + + const historyTable = document.createElement('div'); + historyTable.id = 'history-table'; + historyCol.appendChild(historyTable); + + grid.append(saveLoadCol, historyCol); + container.appendChild(grid); + + renderHistoryTable(); + + saveBtn.addEventListener('click', () => { + const blob = new Blob([saveToJSON(state)], { type: 'application/json' }); + downloadBlob(blob, 'morning-planner-daten.json'); + }); + + loadBtn.addEventListener('click', () => fileInput.click()); + + fileInput.addEventListener('change', async e => { + const file = e.target.files[0]; + if (!file) return; + try { + const loaded = loadFromJSON(await file.text()); + state.employees = loaded.employees; + state.calendar = loaded.calendar; + state.history = loaded.history; + alert('Daten erfolgreich geladen.'); + renderDataTab(); + } catch (err) { + alert('Fehler beim Laden: ' + err.message); + } + e.target.value = ''; + }); +} + +function renderHistoryTable() { + const el = document.getElementById('history-table'); + el.replaceChildren(); + + if (state.history.length === 0) { + const msg = document.createElement('p'); + msg.style.cssText = 'color:#9ca3af;font-size:13px'; + msg.textContent = 'Noch keine Einträge im Verlauf.'; + el.appendChild(msg); + return; + } + + const nameById = Object.fromEntries(state.employees.map(e => [e.id, e.name])); + const sorted = [...state.history].sort((a, b) => b.date.localeCompare(a.date)); + + const table = document.createElement('table'); + const thead = document.createElement('thead'); + const headRow = document.createElement('tr'); + ['Datum','Mitarbeiter','Typ',''].forEach(txt => { + const th = document.createElement('th'); + th.textContent = txt; + headRow.appendChild(th); + }); + thead.appendChild(headRow); + table.appendChild(thead); + + const tbody = document.createElement('tbody'); + for (const h of sorted) { + const [y, m, d] = h.date.split('-'); + const tr = document.createElement('tr'); + + const dateTd = document.createElement('td'); + dateTd.textContent = d + '.' + m + '.' + y; + + const nameTd = document.createElement('td'); + nameTd.textContent = nameById[h.employeeId] ?? '(unbekannt)'; // textContent: safe + + const typeTd = document.createElement('td'); + typeTd.style.color = '#6b7280'; + typeTd.textContent = h.manual ? 'Manuell' : 'Auto'; + + const actionTd = document.createElement('td'); + const delBtn = document.createElement('button'); + delBtn.type = 'button'; + delBtn.textContent = '✕'; + delBtn.style.cssText = 'font-size:11px;padding:2px 6px'; + delBtn.addEventListener('click', () => { + removeHistoryEntry(state, h.date); + renderHistoryTable(); + }); + actionTd.appendChild(delBtn); + + tr.append(dateTd, nameTd, typeTd, actionTd); + tbody.appendChild(tr); + } + table.appendChild(tbody); + el.appendChild(table); +}