feat: data tab with JSON load/save and history management
This commit is contained in:
148
js/history.js
Normal file
148
js/history.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user