From 836d0eeda5c154a8c9c7d3c377d1a73fb6be6e8a Mon Sep 17 00:00:00 2001 From: Ferdinand Urban Date: Wed, 3 Jun 2026 13:44:45 +0000 Subject: [PATCH] chore: unify name to "Meeting Mixer", README tech stack, name magic numbers (#16) - Canonical name "Meeting Mixer" everywhere: , 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> --- .vch-description | 1 + README.md | 18 +++++++++++++++--- index.html | 2 +- js/algorithm.js | 5 ++++- js/app.js | 4 +++- js/calendar.js | 5 +++-- js/data.js | 6 ++++-- js/utils.js | 7 ++++++- package.json | 2 +- 9 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 .vch-description diff --git a/.vch-description b/.vch-description new file mode 100644 index 0000000..d8e11cc --- /dev/null +++ b/.vch-description @@ -0,0 +1 @@ +Meeting Mixer – Werkzeug zur fairen, automatischen Einteilung von Moderatoren für wiederkehrende Meetings. Es verteilt die Moderation gleichmäßig über das Team, berücksichtigt Verfügbarkeiten und Urlaube und erstellt fertige Monatspläne zum Export. Für Teams, die ihre Meeting-Moderation organisiert rotieren möchten. diff --git a/README.md b/README.md index 3b3f42f..ad758eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Morning Meeting Planner +# Meeting Mixer -Web-App zur Planung und Verwaltung von Morning-Meeting-Moderatoren. +Web-App zur Planung und Verwaltung von Meeting-Moderatoren. ## Features @@ -12,13 +12,25 @@ Web-App zur Planung und Verwaltung von Morning-Meeting-Moderatoren. - **Verlauf** – Vergangene Monatspläne einsehen und wiederherstellen - **Persistenz** – Automatische Speicherung im Browser (localStorage) +## Tech-Stack + +- **Vanilla JavaScript (ES-Module)** – kein Framework, **kein Build-Schritt** +- **Persistenz:** Browser-`localStorage` (kein Backend, keine Datenbank) +- **Excel-Im-/Export:** [SheetJS](https://sheetjs.com/) (per CDN, mit SRI eingebunden) +- **Statisches Serving:** [`serve`](https://www.npmjs.com/package/serve) (Dev) +- **Tests:** Node-Assertions (`node:assert`), ohne externe Abhängigkeiten + +Voraussetzung: ein moderner Browser mit ES-Modul-Unterstützung. + ## Starten ```bash npm start ``` -Die App läuft auf **http://localhost:3001**. +Startet einen statischen Server. Der genutzte Port wird beim Start ausgegeben +(Standard 3001; weicht automatisch aus, falls belegt). Öffne anschließend die +angezeigte URL im Browser. ## Tests diff --git a/index.html b/index.html index 372531e..3dfff57 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.sheetjs.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data:; object-src 'none'; base-uri 'self'"> - <title>Morning Meeting Planner + Meeting Mixer diff --git a/js/algorithm.js b/js/algorithm.js index b7bddfe..64a88cf 100644 --- a/js/algorithm.js +++ b/js/algorithm.js @@ -1,5 +1,8 @@ import { toISO, getWeekday } from './utils.js'; +// Score penalty applied when a candidate is low-priority on the given weekday. +const LOW_PRIORITY_PENALTY = 5; + function isOnVacation(employee, isoDate) { return employee.constraints.vacations.some(v => v.from <= isoDate && isoDate <= v.to); } @@ -60,7 +63,7 @@ function scoreCandidate(employee, isoDate, histCount, randomFn) { let score = -myTotal; // Low priority: increase negative score (less likely to be picked) - if (employee.constraints.lowPriorityDays.includes(wd)) score -= 5; + if (employee.constraints.lowPriorityDays.includes(wd)) score -= LOW_PRIORITY_PENALTY; // Jitter ±0.49 score += (randomFn() - 0.5) * 0.98; diff --git a/js/app.js b/js/app.js index db0463f..a7b55d3 100644 --- a/js/app.js +++ b/js/app.js @@ -2,6 +2,7 @@ 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'; @@ -22,7 +23,8 @@ try { 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 >= 1 && parsed.settings.moderatorsPerDay <= 5) { + && parsed.settings.moderatorsPerDay >= MIN_MODERATORS_PER_DAY + && parsed.settings.moderatorsPerDay <= MAX_MODERATORS_PER_DAY) { state.settings = { moderatorsPerDay: parsed.settings.moderatorsPerDay }; } } diff --git a/js/calendar.js b/js/calendar.js index 01bfcbb..edc1242 100644 --- a/js/calendar.js +++ b/js/calendar.js @@ -2,7 +2,8 @@ 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'; -import { toISO, monthPrefix, getWeekday, buildNameMap } from './utils.js'; +import { toISO, monthPrefix, getWeekday, buildNameMap, + MIN_MODERATORS_PER_DAY, MAX_MODERATORS_PER_DAY } from './utils.js'; const MONTHS = ['Januar','Februar','März','April','Mai','Juni', 'Juli','August','September','Oktober','November','Dezember']; @@ -61,7 +62,7 @@ export function renderCalendar() { const slotsSel = document.createElement('select'); slotsSel.id = 'cal-slots'; - for (let n = 1; n <= 5; n++) { + for (let n = MIN_MODERATORS_PER_DAY; n <= MAX_MODERATORS_PER_DAY; n++) { const opt = document.createElement('option'); opt.value = String(n); opt.textContent = String(n); diff --git a/js/data.js b/js/data.js index af47ae6..5f42fe9 100644 --- a/js/data.js +++ b/js/data.js @@ -1,3 +1,5 @@ +import { MIN_MODERATORS_PER_DAY, MAX_MODERATORS_PER_DAY } from './utils.js'; + function generateId() { if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { return crypto.randomUUID(); @@ -17,8 +19,8 @@ function generateId() { function parseSettings(raw) { const n = raw?.moderatorsPerDay; - const valid = Number.isInteger(n) && n >= 1 && n <= 5; - return { moderatorsPerDay: valid ? n : 1 }; + const valid = Number.isInteger(n) && n >= MIN_MODERATORS_PER_DAY && n <= MAX_MODERATORS_PER_DAY; + return { moderatorsPerDay: valid ? n : MIN_MODERATORS_PER_DAY }; } function defaultConstraints() { diff --git a/js/utils.js b/js/utils.js index bd5da79..ba78e0a 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1,4 +1,9 @@ -// Shared pure helpers used across modules. +// Shared pure helpers and constants used across modules. + +// Allowed range for the "moderators per day" setting. +export const MIN_MODERATORS_PER_DAY = 1; +export const MAX_MODERATORS_PER_DAY = 5; + // "YYYY-MM-DD" from numeric parts. export function toISO(year, month, day) { diff --git a/package.json b/package.json index 124ec8d..a13c7b5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "morning-meeting-planner", + "name": "meeting-mixer", "version": "1.0.0", "private": true, "type": "module",