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