chore: cleanup dead code and duplicated logic from audit

- Remove broken webpack.yml (no package.json/webpack config exists)
- Drop dead RATE_NORMAL/RATE_WEEKEND fields on BonusCalculator; share the
  real rate constants across all three variants in variants.js
- Simplify getEmptyResult() to build allResults via .map instead of
  three manual spreads
- Replace 5 duplicated German month-name arrays and 2 weekday-name
  arrays in app.js with a shared monthName()/WEEKDAY_NAMES helper;
  drop the now-redundant getMonthName() method
- Reuse existing .modal/.modal-backdrop/.modal-content CSS classes in
  generateEmailReport() instead of hand-rolled inline styles
- Delegate ImageImporter.classify() to the shared classify() in
  variants.js instead of a duplicated copy
- Unify ad hoc YYYY-MM-DD formatting in app.js to use the existing
  HolidayProvider.formatDate() helper

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kenearos 2026-07-05 19:23:14 +02:00
parent a6be57e964
commit 722f961387
5 changed files with 35 additions and 90 deletions

View file

@ -1,28 +0,0 @@
name: NodeJS with Webpack
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Build
run: |
npm install
npx webpack

57
app.js
View file

@ -2,6 +2,11 @@
* Main Application * Main Application
* Manages UI interactions and coordinates between components * Manages UI interactions and coordinates between components
*/ */
function monthName(monthNumber) {
return new Intl.DateTimeFormat('de-DE', { month: 'long' }).format(new Date(2000, monthNumber - 1, 1));
}
const WEEKDAY_NAMES = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
class DienstplanApp { class DienstplanApp {
constructor() { constructor() {
this.storage = new DataStorage(); this.storage = new DataStorage();
@ -134,7 +139,7 @@ class DienstplanApp {
document.getElementById('calc-year-select').value = currentYear; document.getElementById('calc-year-select').value = currentYear;
// Set date input to today // Set date input to today
const today = new Date().toISOString().split('T')[0]; const today = this.holidayProvider.formatDate(new Date());
document.getElementById('duty-date').value = today; document.getElementById('duty-date').value = today;
this.updateDateStepperState(); this.updateDateStepperState();
@ -330,10 +335,7 @@ class DienstplanApp {
const newDay = curDay + delta; const newDay = curDay + delta;
if (newDay < 1 || newDay > lastDay) return; // clamp if (newDay < 1 || newDay > lastDay) return; // clamp
const newDate = new Date(year, month - 1, newDay, 12, 0, 0); const newDate = new Date(year, month - 1, newDay, 12, 0, 0);
const yyyy = newDate.getFullYear(); dateInput.value = this.holidayProvider.formatDate(newDate);
const mm = String(newDate.getMonth() + 1).padStart(2, '0');
const dd = String(newDate.getDate()).padStart(2, '0');
dateInput.value = `${yyyy}-${mm}-${dd}`;
this.updateDateStepperState(); this.updateDateStepperState();
} }
@ -402,9 +404,7 @@ class DienstplanApp {
const duties = this.storage.getDutiesForMonth(employeeName, year, month); const duties = this.storage.getDutiesForMonth(employeeName, year, month);
if (duties.length === 0) { if (duties.length === 0) {
const monthNames = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', container.innerHTML = `<p class="text-muted">Keine Dienste für ${monthName(month)} ${year}.</p>`;
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
container.innerHTML = `<p class="text-muted">Keine Dienste für ${monthNames[month - 1]} ${year}.</p>`;
return; return;
} }
@ -463,10 +463,7 @@ class DienstplanApp {
const results = this.calculator.calculateAllEmployees(employeeDuties, vacationMap); const results = this.calculator.calculateAllEmployees(employeeDuties, vacationMap);
const monthNames = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', resultsContainer.innerHTML = `<h3>Ergebnisse für ${monthName(month)} ${year}</h3>`;
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
resultsContainer.innerHTML = `<h3>Ergebnisse für ${monthNames[month - 1]} ${year}</h3>`;
const employees = Object.keys(results); const employees = Object.keys(results);
if (employees.length === 0) { if (employees.length === 0) {
@ -631,9 +628,9 @@ class DienstplanApp {
}); });
const results = this.calculator.calculateAllEmployees(employeeDuties, vacationMap); const results = this.calculator.calculateAllEmployees(employeeDuties, vacationMap);
const monthName = this.getMonthName(month); const monthLabel = monthName(month);
let reportHtml = `<h3>Dienstplan Abrechnung ${monthName} ${year}</h3>`; let reportHtml = `<h3>Dienstplan Abrechnung ${monthLabel} ${year}</h3>`;
// 1. Copy-Paste Table // 1. Copy-Paste Table
reportHtml += `<div style="background: #ffffff; padding: 15px; border: 1px solid #ddd;">`; reportHtml += `<div style="background: #ffffff; padding: 15px; border: 1px solid #ddd;">`;
@ -702,16 +699,17 @@ class DienstplanApp {
// Modal Logic // Modal Logic
const modal = document.createElement('div'); const modal = document.createElement('div');
modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:1000;'; modal.className = 'modal';
modal.innerHTML = ` modal.innerHTML = `
<div style="background:white;padding:20px;border-radius:8px;max-width:800px;width:90%;max-height:90vh;overflow-y:auto;position:relative;box-shadow: 0 4px 6px rgba(0,0,0,0.1);"> <div class="modal-backdrop"></div>
<button id="close-modal-btn" style="position:absolute;top:10px;right:10px;border:none;background:none;font-size:24px;cursor:pointer;">&times;</button> <div class="modal-content">
<button class="modal-close" id="close-modal-btn" aria-label="Schliessen">&times;</button>
<h2 style="margin-top:0;">📧 E-Mail Text-Generator</h2> <h2 style="margin-top:0;">📧 E-Mail Text-Generator</h2>
<p class="text-muted">Kopieren Sie diesen Inhalt direkt in Ihre E-Mail an die Verwaltung.</p> <p class="text-muted">Kopieren Sie diesen Inhalt direkt in Ihre E-Mail an die Verwaltung.</p>
<div id="report-content"> <div id="report-content">
${reportHtml} ${reportHtml}
</div> </div>
<div style="margin-top:20px;text-align:right;border-top: 1px solid #eee; padding-top: 15px;"> <div class="modal-actions">
<button id="copy-btn" class="btn btn-primary" style="font-size: 1.1em;">📋 Alles markieren & kopieren</button> <button id="copy-btn" class="btn btn-primary" style="font-size: 1.1em;">📋 Alles markieren & kopieren</button>
<button id="close-btn-bottom" class="btn btn-secondary">Schließen</button> <button id="close-btn-bottom" class="btn btn-secondary">Schließen</button>
</div> </div>
@ -719,6 +717,7 @@ class DienstplanApp {
`; `;
document.body.appendChild(modal); document.body.appendChild(modal);
modal.querySelector('.modal-backdrop').onclick = () => modal.remove();
modal.querySelector('#close-modal-btn').onclick = () => modal.remove(); modal.querySelector('#close-modal-btn').onclick = () => modal.remove();
modal.querySelector('#close-btn-bottom').onclick = () => modal.remove(); modal.querySelector('#close-btn-bottom').onclick = () => modal.remove();
@ -761,9 +760,6 @@ class DienstplanApp {
* Exports all duties and monthly summary for the selected month * Exports all duties and monthly summary for the selected month
*/ */
exportCSV() { exportCSV() {
const monthNames = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
const month = parseInt(document.getElementById('calc-month-select').value); const month = parseInt(document.getElementById('calc-month-select').value);
const year = parseInt(document.getElementById('calc-year-select').value); const year = parseInt(document.getElementById('calc-year-select').value);
@ -781,7 +777,7 @@ class DienstplanApp {
let csv = '\uFEFF'; // UTF-8 BOM for Excel let csv = '\uFEFF'; // UTF-8 BOM for Excel
// === Sheet 1: Dienste (All Duties for the month) === // === Sheet 1: Dienste (All Duties for the month) ===
csv += `DIENSTE ${monthNames[month - 1]} ${year}\n`; csv += `DIENSTE ${monthName(month)} ${year}\n`;
csv += 'Datum;Wochentag;Mitarbeiter;Anteil;Tagestyp\n'; csv += 'Datum;Wochentag;Mitarbeiter;Anteil;Tagestyp\n';
const employees = this.storage.getEmployees(); const employees = this.storage.getEmployees();
@ -804,7 +800,7 @@ class DienstplanApp {
allDuties.forEach(duty => { allDuties.forEach(duty => {
const isQual = this.calculator.isQualifyingDay(duty.date); const isQual = this.calculator.isQualifyingDay(duty.date);
const dateStr = duty.date.toLocaleDateString('de-DE'); const dateStr = duty.date.toLocaleDateString('de-DE');
const weekday = weekdays[duty.date.getDay()]; const weekday = WEEKDAY_NAMES[duty.date.getDay()];
const dayType = isQual ? 'WE-Tag' : 'Werktag (WT)'; const dayType = isQual ? 'WE-Tag' : 'Werktag (WT)';
csv += `${dateStr};${weekday};${escapeCSV(duty.employee)};${duty.share.toFixed(1).replace('.', ',')};${dayType}\n`; csv += `${dateStr};${weekday};${escapeCSV(duty.employee)};${duty.share.toFixed(1).replace('.', ',')};${dayType}\n`;
@ -813,7 +809,7 @@ class DienstplanApp {
csv += '\n\n'; csv += '\n\n';
// === Sheet 2: Monatliche Auswertung === // === Sheet 2: Monatliche Auswertung ===
csv += `AUSWERTUNG ${monthNames[month - 1]} ${year}\n`; csv += `AUSWERTUNG ${monthName(month)} ${year}\n`;
csv += 'Mitarbeiter;Urlaub;Sieger-Variante;Fr;Sa;So;Werktage;Eligible;Abzug Fr;Abzug Sa;Abzug So;Abzug WT;Bonus (EUR)\n'; csv += 'Mitarbeiter;Urlaub;Sieger-Variante;Fr;Sa;So;Werktage;Eligible;Abzug Fr;Abzug Sa;Abzug So;Abzug WT;Bonus (EUR)\n';
const yearMonth = `${year}-${String(month).padStart(2, '0')}`; const yearMonth = `${year}-${String(month).padStart(2, '0')}`;
@ -875,9 +871,6 @@ class DienstplanApp {
* Opens in a new window for printing or saving as PDF * Opens in a new window for printing or saving as PDF
*/ */
exportBonusReport() { exportBonusReport() {
const monthNames = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
const weekdays = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
const month = parseInt(document.getElementById('calc-month-select').value); const month = parseInt(document.getElementById('calc-month-select').value);
const year = parseInt(document.getElementById('calc-year-select').value); const year = parseInt(document.getElementById('calc-year-select').value);
@ -926,7 +919,7 @@ class DienstplanApp {
<html lang="de"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Bonuszahlungen ${monthNames[month - 1]} ${year}</title> <title>Bonuszahlungen ${monthName(month)} ${year}</title>
<style> <style>
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
@ -1030,9 +1023,9 @@ class DienstplanApp {
</div> </div>
<h3>Bonuszahlungen</h3> <h3>Bonuszahlungen</h3>
<h5>Monat ${monthNames[month - 1]} ${year} mit Auszahlung Ende ${monthNames[payoutMonth]} ${payoutYear}</h5> <h5>Monat ${monthName(month)} ${year} mit Auszahlung Ende ${monthName(payoutMonth + 1)} ${payoutYear}</h5>
<p>Für die im ${monthNames[month - 1]} ${year} geleisteten Bereitschaftsdienste ergeben sich folgende Bonuszahlungen:</p> <p>Für die im ${monthName(month)} ${year} geleisteten Bereitschaftsdienste ergeben sich folgende Bonuszahlungen:</p>
<table> <table>
<thead> <thead>
@ -1247,10 +1240,6 @@ class DienstplanApp {
return num.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 1 }); return num.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 1 });
} }
getMonthName(monthIndex) {
const names = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
return names[monthIndex - 1];
}
} }
// Initialize app when DOM is ready // Initialize app when DOM is ready

View file

@ -6,8 +6,6 @@
class BonusCalculator { class BonusCalculator {
constructor(holidayProvider) { constructor(holidayProvider) {
this.holidayProvider = holidayProvider; this.holidayProvider = holidayProvider;
this.RATE_NORMAL = 250;
this.RATE_WEEKEND = 450;
} }
/** /**
@ -111,16 +109,13 @@ class BonusCalculator {
deduction: { fr: 0, sa: 0, so: 0, weekday: 0 }, deduction: { fr: 0, sa: 0, so: 0, weekday: 0 },
paidShares: { fr: 0, sa: 0, so: 0, weekday: 0 }, paidShares: { fr: 0, sa: 0, so: 0, weekday: 0 },
bonus: 0, bonus: 0,
isWinner: true isWinner: false
}; };
return { return {
classified: { fr: 0, sa: 0, so: 0, weekday: 0 }, classified: { fr: 0, sa: 0, so: 0, weekday: 0 },
isVacation, isVacation,
winner: empty, winner: { ...empty, isWinner: true },
allResults: [empty, allResults: [1, 2, 3].map(variantId => ({ ...empty, variantId, isWinner: variantId === 1 })),
{ ...empty, variantId: 2, isWinner: false },
{ ...empty, variantId: 3, isWinner: false }
],
totalBonus: 0, totalBonus: 0,
totalDuties: 0, totalDuties: 0,
dutyDetails: [] dutyDetails: []

View file

@ -258,21 +258,14 @@ class ImageImporter {
} }
/** /**
* Slot classification, duplicated from Feature B per spec section 9.3 (independent feature). * Slot classification. Delegates to the shared classify() from variants.js;
* falls back to a no-holidays provider when this.holidayProvider is unset.
* @param {Date} date * @param {Date} date
* @returns {'fr'|'sa'|'so'|'weekday'} * @returns {'fr'|'sa'|'so'|'weekday'}
*/ */
classify(date) { classify(date) {
const wd = date.getDay(); const holidayProvider = this.holidayProvider || { isHoliday: () => false, isDayBeforeHoliday: () => false };
if (wd === 5) return 'fr'; return window.classify(date, holidayProvider);
if (wd === 6) return 'sa';
if (wd === 0) return 'so';
const isFeiertag = this.holidayProvider && this.holidayProvider.isHoliday(date);
const isTagVorFeiertag = this.holidayProvider && this.holidayProvider.isDayBeforeHoliday(date);
if (isFeiertag && isTagVorFeiertag) return 'sa';
if (isTagVorFeiertag) return 'fr';
if (isFeiertag) return 'so';
return 'weekday';
} }
/** /**

View file

@ -4,7 +4,9 @@
* Loaded after holidays.js and before calculator.js. * Loaded after holidays.js and before calculator.js.
*/ */
// Will be implemented in subsequent tasks. const RATE_NORMAL = 250;
const RATE_WEEKEND = 450;
function classify(date, holidayProvider) { function classify(date, holidayProvider) {
const wd = date.getDay(); // 0=So, 1=Mo, ..., 5=Fr, 6=Sa const wd = date.getDay(); // 0=So, 1=Mo, ..., 5=Fr, 6=Sa
@ -34,8 +36,6 @@ function classifyDuties(duties, holidayProvider) {
} }
function variant1(classified, isVacation) { function variant1(classified, isVacation) {
const RATE_NORMAL = 250;
const RATE_WEEKEND = 450;
const frSoThreshold = isVacation ? 0.5 : 1; const frSoThreshold = isVacation ? 0.5 : 1;
const weekdayThreshold = isVacation ? 1.5 : 3; const weekdayThreshold = isVacation ? 1.5 : 3;
const frSoDeduction = isVacation ? 0.5 : 1; const frSoDeduction = isVacation ? 0.5 : 1;
@ -89,8 +89,6 @@ function variant1(classified, isVacation) {
} }
function variant2(classified, isVacation) { function variant2(classified, isVacation) {
const RATE_NORMAL = 250;
const RATE_WEEKEND = 450;
const saThreshold = isVacation ? 0.5 : 1; const saThreshold = isVacation ? 0.5 : 1;
const weekdayThreshold = isVacation ? 1 : 2; const weekdayThreshold = isVacation ? 1 : 2;
const saDeduction = isVacation ? 0.5 : 1; const saDeduction = isVacation ? 0.5 : 1;
@ -135,8 +133,6 @@ function variant2(classified, isVacation) {
} }
function variant3(classified, isVacation) { function variant3(classified, isVacation) {
const RATE_NORMAL = 250;
const RATE_WEEKEND = 450;
const poolThreshold = isVacation ? 1 : 2; const poolThreshold = isVacation ? 1 : 2;
const totalDeduction = isVacation ? 1 : 2; const totalDeduction = isVacation ? 1 : 2;