From dca82fe381b8c9db33c4469ea0dea8b8a37193d9 Mon Sep 17 00:00:00 2001 From: Ferdinand Date: Wed, 8 Apr 2026 14:17:24 +0200 Subject: [PATCH] feat: auto-persist state to localStorage on every change --- js/app.js | 28 ++++++++++++++++++++++++++++ js/calendar.js | 9 ++++++--- js/employees.js | 13 ++++++++++--- js/history.js | 4 +++- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/js/app.js b/js/app.js index d5d94e3..08ecc9e 100644 --- a/js/app.js +++ b/js/app.js @@ -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', () => { diff --git a/js/calendar.js b/js/calendar.js index d6a127e..6e8746a 100644 --- a/js/calendar.js +++ b/js/calendar.js @@ -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); diff --git a/js/employees.js b/js/employees.js index 97700b2..e9700b0 100644 --- a/js/employees.js +++ b/js/employees.js @@ -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); }); diff --git a/js/history.js b/js/history.js index 34e24e6..4173383 100644 --- a/js/history.js +++ b/js/history.js @@ -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);