Files
Meeting-Mixer/js/app.js
T
ferdi2goandClaude Opus 4.8 836d0eeda5 chore: unify name to "Meeting Mixer", README tech stack, name magic numbers (#16)
- Canonical name "Meeting Mixer" everywhere: <title>, README, .vch-description
  (now non-technical per convention) and package.json (meeting-mixer).
- README gains a Tech-Stack section and an accurate, non-misleading port note.
- Magic numbers replaced by named constants: LOW_PRIORITY_PENALTY in
  algorithm.js; MIN/MAX_MODERATORS_PER_DAY in utils.js, used by data.js,
  calendar.js and app.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:44:45 +00:00

87 lines
3.2 KiB
JavaScript

import { renderEmployees } from './employees.js';
import { renderCalendar } from './calendar.js';
import { renderDataTab } from './history.js';
import { normalizeEmployees } from './data.js';
import { MIN_MODERATORS_PER_DAY, MAX_MODERATORS_PER_DAY } from './utils.js';
const STORAGE_KEY = 'morning-planner-state';
// Shared mutable state — imported by all other modules
export const state = {
employees: [],
calendar: { holidays: [], companyClosures: [] },
history: [],
settings: { moderatorsPerDay: 1 }
};
// 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 = normalizeEmployees(parsed.employees);
if (parsed.calendar) state.calendar = parsed.calendar;
if (Array.isArray(parsed.history)) state.history = parsed.history;
if (parsed.settings && Number.isInteger(parsed.settings.moderatorsPerDay)
&& parsed.settings.moderatorsPerDay >= MIN_MODERATORS_PER_DAY
&& parsed.settings.moderatorsPerDay <= MAX_MODERATORS_PER_DAY) {
state.settings = { moderatorsPerDay: parsed.settings.moderatorsPerDay };
}
}
} catch (e) {
// Corrupt storage — start fresh, but leave a breadcrumb for debugging
console.warn('Gespeicherter Zustand konnte nicht gelesen werden, starte leer:', e);
}
// Persist immediately. Used by the debounced saveState and the flush handlers.
let storageWarned = false;
let saveTimer = null;
function writeState() {
if (saveTimer !== null) { clearTimeout(saveTimer); saveTimer = null; }
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
employees: state.employees,
calendar: state.calendar,
history: state.history,
settings: state.settings
}));
} catch (e) {
// Storage full or unavailable — warn once so the user can react (export data)
console.warn('Speichern in localStorage fehlgeschlagen:', e);
if (!storageWarned) {
storageWarned = true;
alert('Achtung: Änderungen konnten nicht gespeichert werden ' +
'(Speicher voll oder nicht verfügbar). Bitte exportiere deine Daten ' +
'als JSON, um Datenverlust zu vermeiden.');
}
}
}
// Call after any state mutation. Debounced to coalesce rapid changes; a pending
// write is flushed on page hide/unload so the last change is never lost.
export function saveState() {
if (saveTimer !== null) clearTimeout(saveTimer);
saveTimer = setTimeout(writeState, 400);
}
const flushPending = () => { if (saveTimer !== null) writeState(); };
window.addEventListener('beforeunload', flushPending);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flushPending();
});
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(s => s.classList.add('hidden'));
btn.classList.add('active');
document.getElementById('tab-' + tab).classList.remove('hidden');
if (tab === 'planning') renderCalendar();
if (tab === 'data') renderDataTab();
});
});
renderEmployees();