# CLAUDE.md - AI Assistant Guide for Dienstplan-Pro ## Project Overview **Dienstplan-Pro** is a German-language Progressive Web App (PWA) for calculating bonus payments for weekend and holiday duty shifts according to NRW (Nordrhein-Westfalen) regulations. The application is designed for healthcare or similar organizations that need to track employee on-call duties and calculate corresponding bonuses. **Primary Language**: German (UI, comments, and documentation) **Tech Stack**: Vanilla JavaScript, HTML5, CSS3, LocalStorage API **Deployment**: Docker container (Node.js `serve`) on self-hosted Hetzner server, fronted by Caddy reverse proxy with automatic Let's Encrypt TLS. Live at https://bonus.pixel-by-design.de ## Architecture ### File Structure ``` Dienstplan-Pro/ ├── index.html # Main HTML entry point with tab-based UI ├── app.js # Main application class (DienstplanApp) - UI management ├── calculator.js # BonusCalculator class - core business logic ├── holidays.js # HolidayProvider class - NRW holiday data 2025-2030 ├── storage.js # DataStorage class - LocalStorage persistence ├── styles.css # All CSS styles (responsive, gradient theme) ├── sw.js # Service Worker for PWA offline support ├── manifest.json # PWA manifest configuration ├── test-suite.js # Comprehensive test runner with assertions ├── test.html # Browser-based test interface ├── Dockerfile # Production deployment configuration ├── README.md # User documentation (German) └── TEST_GUIDE.md # Testing documentation (German) ``` ### Module Responsibilities | Module | Class | Purpose | |--------|-------|---------| | `app.js` | `DienstplanApp` | UI orchestration, event handling, user interactions | | `calculator.js` | `BonusCalculator` | Bonus calculation logic, day type classification | | `holidays.js` | `HolidayProvider` | NRW public holiday lookup (2025-2030) | | `storage.js` | `DataStorage` | LocalStorage CRUD operations, import/export | ### Global Dependencies All classes are attached to `window` for cross-module access: - `window.DienstplanApp` (instantiated as global `app`) - `window.BonusCalculator` - `window.HolidayProvider` - `window.DataStorage` Script loading order in `index.html` is critical: 1. `holidays.js` 2. `calculator.js` 3. `storage.js` 4. `app.js` ## Business Logic ### Qualifying Days (WE/Feiertag) A day is "qualifying" (eligible for higher bonus rate) if ANY of: - **Weekend**: Friday (5), Saturday (6), or Sunday (0) - **Public Holiday**: Any NRW state holiday - **Day Before Holiday**: The calendar day preceding a public holiday ### Bonus Calculation Rules ``` Constants: - RATE_NORMAL = 250€ (normal weekday rate) - RATE_WEEKEND = 450€ (qualifying day rate) - MIN_QUALIFYING_DAYS = 2.0 (threshold) - DEDUCTION_AMOUNT = 2.0 (deducted from qualifying days) Algorithm: 1. Count qualifying days (Friday, Sat, Sun, holidays, day-before-holiday) 2. Count normal days (Mon-Thu, not holiday-related) 3. If qualifyingDays < 2.0: NO BONUS (total = 0€) 4. If qualifyingDays >= 2.0: - Deduct 2.0 from qualifying days (Friday priority) - Bonus = (normalDays × 250€) + (remainingQualifyingDays × 450€) ``` ### Friday Priority Deduction When deducting the 2.0 qualifying days, Fridays are deducted first before Saturday/Sunday/holidays. This is tracked via `qualifyingDaysFriday` and `qualifyingDaysOther` in the calculator. ### Duty Shares Duties can be full (1.0) or half (0.5). Half duties count as 0.5 toward all calculations. ## Data Storage ### LocalStorage Keys ```javascript STORAGE_KEY_EMPLOYEES = 'dienstplan_employees' // Array of employee names STORAGE_KEY_DUTIES = 'dienstplan_duties' // Nested duty object ``` ### Data Structure ```javascript // Employees: string[] ["Max Mustermann", "Anna Schmidt"] // Duties: { employeeName: { "YYYY-MM": duties[] } } { "Max Mustermann": { "2025-11": [ { "date": "2025-11-22T11:00:00.000Z", "share": 1.0 }, { "date": "2025-11-23T11:00:00.000Z", "share": 0.5 } ] } } ``` ### Date Handling - Dates are stored as ISO strings in LocalStorage - Use `T12:00:00` when creating dates to avoid timezone edge cases - Dates are converted back to Date objects when retrieved ## UI Structure The app uses a tab-based interface with 4 sections: 1. **Dienste eintragen** (Enter Duties) - Add/remove shifts 2. **Berechnung** (Calculation) - Calculate and view bonuses 3. **Mitarbeiter verwalten** (Manage Employees) - CRUD for employees 4. **Einstellungen** (Settings) - Export/import, rules info, data clearing ### Toast Notifications Use `app.showToast(message, type)` where type is: - `'success'` - Green - `'error'` - Red - `'info'` - Blue ## Testing ### Running Tests 1. Serve the app: `python3 -m http.server 8000` or use the Docker container 2. Open `http://localhost:8000/test.html` 3. Click "Alle Tests ausführen" ### Test Categories - **HolidayProvider**: Holiday detection, day-before-holiday - **Calculator - Tag-Klassifizierung**: Day type classification - **Calculator - Bonusberechnung**: Bonus calculation scenarios - **Storage**: CRUD operations, import/export - **Edge Cases**: Rounding, performance, leap years ### Adding Tests ```javascript runner.test('Test Name', (t) => { const calculator = new BonusCalculator(new HolidayProvider()); const duties = [{ date: new Date('2025-11-22T12:00:00'), share: 1.0 }]; const result = calculator.calculateMonthlyBonus(duties); t.assertEqual(result.totalBonus, 450, 'Expected bonus'); t.assertTrue(result.thresholdReached, 'Threshold should be reached'); t.assertAlmostEqual(result.qualifyingDays, 1.0, 0.01, 'Qualifying days'); }); ``` ## Development Workflow ### Local Development ```bash # Option 1: Python server python3 -m http.server 8000 # Option 2: Node.js npx http-server -p 8000 # Option 3: Docker docker build -t dienstplan-pro . docker run -p 3000:3000 -e PORT=3000 dienstplan-pro ``` ### Making Changes 1. All JavaScript is vanilla ES6+ classes 2. No build step required 3. Refresh browser to see changes 4. Run test suite after changes ### Deployment (Hetzner) **Live URL:** https://bonus.pixel-by-design.de **Server:** root@65.21.60.83 (Hetzner) **Container:** `dienstplan-pro` on the `matrix_default` Docker network so the `matrix-caddy-1` reverse proxy can resolve it by hostname. The Dockerfile uses `serve` from npm to serve static files: ```dockerfile FROM node:20-alpine RUN npm install -g serve WORKDIR /app COPY . . CMD serve -s . -l tcp://0.0.0.0:${PORT:-3000} ``` Caddy block in `/opt/matrix/Caddyfile`: ``` bonus.pixel-by-design.de { reverse_proxy dienstplan-pro:3000 } ``` **Update procedure** (when pushing new code): ```bash ssh root@65.21.60.83 cd /root/Dienstplan-Pro git pull docker build -t dienstplan-pro:latest . docker stop dienstplan-pro && docker rm dienstplan-pro docker run -d --name dienstplan-pro --network matrix_default \ --restart unless-stopped -e PORT=3000 dienstplan-pro:latest ``` Caddy reloads not needed unless the Caddyfile changes. ## Code Conventions ### Language - All user-facing text: **German** - Code comments: **German** (existing pattern) - Variable/function names: **English** (existing pattern) ### Naming - Classes: PascalCase (`BonusCalculator`, `DataStorage`) - Methods/functions: camelCase (`calculateMonthlyBonus`, `isHoliday`) - Constants: UPPER_SNAKE_CASE (`RATE_NORMAL`, `STORAGE_KEY_DUTIES`) - DOM IDs: kebab-case (`employee-select-duty`, `calc-month-select`) ### Error Handling - Storage operations include try/catch with German console.error messages - User-facing errors shown via toast notifications - Invalid data returns empty arrays/objects rather than throwing ### Date Format - Display: German locale `toLocaleDateString('de-DE')` - Storage: ISO string - Internal: JavaScript Date objects with noon time (`T12:00:00`) ## Common Tasks ### Adding a New Holiday Edit `holidays.js`, add to the appropriate year array: ```javascript { date: 'YYYY-MM-DD', name: 'Holiday Name' } ``` ### Modifying Calculation Rules Edit `calculator.js` constants: ```javascript this.RATE_NORMAL = 250; this.RATE_WEEKEND = 450; this.MIN_QUALIFYING_DAYS = 2.0; this.DEDUCTION_AMOUNT = 2.0; ``` ### Adding Export Formats The app supports multiple export formats in `app.js`: - `exportData()` - JSON backup - `exportCSV()` - Excel-compatible CSV with BOM - `exportBonusReport()` - HTML report for printing/PDF - `generateEmailReport()` - Copyable email text ### Extending the UI 1. Add HTML in `index.html` within appropriate tab-content div 2. Add event listener in `setupEventListeners()` in `app.js` 3. Implement handler method in `DienstplanApp` class 4. Style in `styles.css` following existing patterns ## PWA Features - **Service Worker** (`sw.js`): Caches all assets for offline use - **Manifest** (`manifest.json`): Enables "Add to Home Screen" - **Cache Version**: `dienstplan-pro-v1` (increment when updating assets) ## Key Gotchas 1. **Timezone Issues**: Always use `T12:00:00` when creating dates from strings to avoid midnight edge cases 2. **LocalStorage Limits**: ~5MB, sufficient for typical use but no warning when approaching limit 3. **Float Precision**: Use `assertAlmostEqual` in tests for floating-point comparisons 4. **Script Order**: `holidays.js` must load before `calculator.js` 5. **Employee Deletion**: Removes all associated duties automatically 6. **Duty Updates**: Adding a duty on existing date replaces (not duplicates) ## Git Conventions Recent commit patterns: - `feat:` New features - `fix:` Bug fixes - Keep commit messages concise and descriptive