feat: auto-persist state to localStorage on every change

This commit is contained in:
Ferdinand
2026-04-08 14:17:24 +02:00
parent 6ecf3e4409
commit dca82fe381
4 changed files with 47 additions and 7 deletions

View File

@@ -2,6 +2,8 @@ import { renderEmployees } from './employees.js';
import { renderCalendar } from './calendar.js';
import { renderDataTab } from './history.js';
const STORAGE_KEY = 'morning-planner-state';
// Shared mutable state — imported by all other modules
export const state = {
employees: [],
@@ -9,6 +11,32 @@ export const state = {
history: []
};
// Load persisted state from localStorage on startup
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed.employees)) state.employees = parsed.employees;
if (parsed.calendar) state.calendar = parsed.calendar;
if (Array.isArray(parsed.history)) state.history = parsed.history;
}
} catch {
// Corrupt storage — start fresh
}
// Call after any state mutation to persist automatically
export function saveState() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
employees: state.employees,
calendar: state.calendar,
history: state.history
}));
} catch {
// Storage full or unavailable — silently ignore
}
}
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {

View File

@@ -1,4 +1,4 @@
import { state } from './app.js';
import { state, saveState } from './app.js';
import { getWorkdays, generatePlan } from './algorithm.js';
import { toggleHoliday, toggleClosure, addHistoryEntries, removeHistoryEntry } from './data.js';
import { exportSchedule, downloadBlob } from './excel.js';
@@ -99,6 +99,7 @@ export function renderCalendar() {
const historyWithoutMonth = state.history.filter(h => !h.date.startsWith(prefix));
currentPlan = generatePlan(workdays, state.employees, historyWithoutMonth);
addHistoryEntries(state, currentPlan);
saveState();
renderGrid(wrapper, exportBtn);
});
@@ -184,6 +185,7 @@ function renderGrid(wrapper, exportBtn) {
removeBtn.addEventListener('click', () => {
if (isHoliday) toggleHoliday(state, iso);
else toggleClosure(state, iso);
saveState();
renderGrid(wrapper, exportBtn);
});
tag.appendChild(removeBtn);
@@ -217,6 +219,7 @@ function renderGrid(wrapper, exportBtn) {
removeHistoryEntry(state, iso);
currentPlan = currentPlan.filter(a => a.date !== iso);
}
saveState();
exportBtn.style.display = currentPlan.length > 0 ? 'inline-block' : 'none';
});
@@ -230,13 +233,13 @@ function renderGrid(wrapper, exportBtn) {
ftBtn.type = 'button';
ftBtn.textContent = 'FT';
ftBtn.style.cssText = 'font-size:9px;padding:1px 4px';
ftBtn.addEventListener('click', () => { toggleHoliday(state, iso); renderGrid(wrapper, exportBtn); });
ftBtn.addEventListener('click', () => { toggleHoliday(state, iso); saveState(); renderGrid(wrapper, exportBtn); });
const buBtn = document.createElement('button');
buBtn.type = 'button';
buBtn.textContent = 'BU';
buBtn.style.cssText = 'font-size:9px;padding:1px 4px';
buBtn.addEventListener('click', () => { toggleClosure(state, iso); renderGrid(wrapper, exportBtn); });
buBtn.addEventListener('click', () => { toggleClosure(state, iso); saveState(); renderGrid(wrapper, exportBtn); });
btnRow.append(ftBtn, buBtn);
cell.appendChild(btnRow);

View File

@@ -1,4 +1,4 @@
import { state } from './app.js';
import { state, saveState } from './app.js';
import { createEmployee, removeEmployee } from './data.js';
import { readEmployeeNames } from './excel.js';
@@ -58,6 +58,7 @@ export function renderEmployees() {
return;
}
nameInput.value = '';
saveState();
refreshList();
});
@@ -76,6 +77,7 @@ export function renderEmployees() {
}
}
e.target.value = '';
saveState();
refreshList();
});
}
@@ -119,6 +121,7 @@ function refreshList() {
removeBtn.addEventListener('click', () => {
removeEmployee(state, emp.id);
saveState();
refreshList();
});
@@ -162,6 +165,7 @@ function renderConstraintEditor(empId) {
const idx = arr.indexOf(day);
if (idx >= 0) arr.splice(idx, 1); else arr.push(day);
btn.classList.toggle('active');
saveState();
});
group.appendChild(btn);
}
@@ -184,6 +188,7 @@ function renderConstraintEditor(empId) {
input.addEventListener('change', () => {
const val = input.value.trim();
emp.constraints[constraintKey] = val === '' ? null : parseInt(val, 10);
saveState();
});
row.append(lbl, input);
return row;
@@ -215,6 +220,7 @@ function renderConstraintEditor(empId) {
addVacBtn.addEventListener('click', () => {
emp.constraints.vacations.push({ from: '', to: '' });
saveState();
renderVacationList(empId);
});
}
@@ -240,7 +246,7 @@ function renderVacationList(empId) {
const fromInput = document.createElement('input');
fromInput.type = 'date';
fromInput.value = v.from;
fromInput.addEventListener('change', () => { emp.constraints.vacations[i].from = fromInput.value; });
fromInput.addEventListener('change', () => { emp.constraints.vacations[i].from = fromInput.value; saveState(); });
const sep = document.createElement('span');
sep.textContent = 'bis';
@@ -248,13 +254,14 @@ function renderVacationList(empId) {
const toInput = document.createElement('input');
toInput.type = 'date';
toInput.value = v.to;
toInput.addEventListener('change', () => { emp.constraints.vacations[i].to = toInput.value; });
toInput.addEventListener('change', () => { emp.constraints.vacations[i].to = toInput.value; saveState(); });
const delBtn = document.createElement('button');
delBtn.type = 'button';
delBtn.textContent = '✕';
delBtn.addEventListener('click', () => {
emp.constraints.vacations.splice(i, 1);
saveState();
renderVacationList(empId);
});

View File

@@ -1,4 +1,4 @@
import { state } from './app.js';
import { state, saveState } from './app.js';
import { loadFromJSON, saveToJSON, removeHistoryEntry } from './data.js';
import { downloadBlob } from './excel.js';
@@ -79,6 +79,7 @@ export function renderDataTab() {
state.employees = loaded.employees;
state.calendar = loaded.calendar;
state.history = loaded.history;
saveState();
alert('Daten erfolgreich geladen.');
renderDataTab();
} catch (err) {
@@ -136,6 +137,7 @@ function renderHistoryTable() {
delBtn.style.cssText = 'font-size:11px;padding:2px 6px';
delBtn.addEventListener('click', () => {
removeHistoryEntry(state, h.date);
saveState();
renderHistoryTable();
});
actionTd.appendChild(delBtn);