diff --git a/app.js b/app.js
index 6884375..b50f8dd 100644
--- a/app.js
+++ b/app.js
@@ -371,70 +371,134 @@ class DienstplanApp {
}
/**
- * Create a result card for an employee
+ * Create a result card for an employee (new variants shape).
*/
createResultCard(employeeName, result) {
const card = document.createElement('div');
card.className = 'result-card';
- let content = `
${employeeName}
`;
+ const ctx = this._currentCalcContext || {};
+ const yearMonth = ctx.yearMonth || '';
+ const vacChecked = result.isVacation ? 'checked' : '';
+ const safeName = String(employeeName).replace(/"/g, '"');
+ const safeYm = String(yearMonth).replace(/"/g, '"');
- if (!result.thresholdReached) {
+ // Header + vacation toggle
+ let content = `
+
+ `;
+
+ if (result.isVacation) {
+ content += `Urlaubsmodus aktiv - Schwellen halbiert
`;
+ }
+
+ // Winner banner
+ if (!result.winner.eligible || result.totalBonus === 0) {
content += `
-
Schwellenwert nicht erreicht
-
Es wurden nur ${result.qualifyingDays.toFixed(1)} qualifizierende Tage gearbeitet.
- Mindestens ${this.calculator.MIN_QUALIFYING_DAYS} Tage erforderlich.
+
Keine Variante triggert
+
Mit den eingetragenen Diensten erreicht keine der drei Varianten einen positiven Bonus.
Keine Bonuszahlung
`;
} else {
content += `
-
-
-
Normale Tage
-
${result.normalDays.toFixed(1)}
-
-
-
WE/Feiertag Tage
-
${result.qualifyingDays.toFixed(1)}
-
-
-
Abzug
-
-${result.qualifyingDaysDeducted.toFixed(1)}
-
-
-
Normale Tage (bezahlt)
-
${result.normalDaysPaid.toFixed(1)}
-
-
-
WE/Feiertag (bezahlt)
-
${result.qualifyingDaysPaid.toFixed(1)}
-
-
-
-
-
-
Normale Tage (250€)
-
${this.calculator.formatCurrency(result.bonusNormalDays)}
-
-
-
WE/Feiertag (450€)
-
${this.calculator.formatCurrency(result.bonusQualifyingDays)}
-
-
-
-
Gesamtbonus
+
Variante ${result.winner.variantId} ★ Sieger
${this.calculator.formatCurrency(result.totalBonus)}
`;
}
+ // Classified summary line
+ const c = result.classified;
+ content += `
+
+ Fr: ${c.fr.toFixed(1)}
+ Sa: ${c.sa.toFixed(1)}
+ So: ${c.so.toFixed(1)}
+ Werktage: ${c.weekday.toFixed(1)}
+
+ `;
+
+ // Collapsible variant breakdown
+ content += `Alle Varianten anzeigen
`;
+ for (const v of result.allResults) {
+ content += this.renderVariantBlock(v, result.winner.variantId);
+ }
+ content += ` `;
+
card.innerHTML = content;
+
+ // Attach vacation-toggle handler
+ const cb = card.querySelector('input[data-vacation-employee]');
+ if (cb) {
+ cb.addEventListener('change', (e) => this.onVacationToggle(e));
+ }
return card;
}
+ /**
+ * Render a single variant sub-panel.
+ */
+ renderVariantBlock(v, winnerId) {
+ const isWinner = v.variantId === winnerId;
+ const star = isWinner ? '★' : '';
+ const labels = {
+ 1: 'V1: 1 (Fr/So) + 3 Werktage',
+ 2: 'V2: 1 Sa + 2 Werktage',
+ 3: 'V3 (loose): 2 qualifizierende Tage (Pool Fr+Sa+So)'
+ };
+ let thresholdStr = '-';
+ if (v.threshold) {
+ if (v.variantId === 1) thresholdStr = `Fr+So ≥ ${v.threshold.frSo}, Werktage ≥ ${v.threshold.weekday}`;
+ if (v.variantId === 2) thresholdStr = `Sa ≥ ${v.threshold.sa}, Werktage ≥ ${v.threshold.weekday}`;
+ if (v.variantId === 3) thresholdStr = `Pool ≥ ${v.threshold.pool}`;
+ }
+ const elig = v.eligible ? 'erfüllt'
+ : 'nicht erfüllt';
+ return `
+
+
+
Schwelle:${thresholdStr}
+
Eligibility:${elig}
+
Abzug:
+ Fr ${v.deduction.fr.toFixed(2)} - Sa ${v.deduction.sa.toFixed(2)} - So ${v.deduction.so.toFixed(2)} - WT ${v.deduction.weekday.toFixed(2)}
+
+
Bezahlt:
+ Fr ${v.paidShares.fr.toFixed(2)} - Sa ${v.paidShares.sa.toFixed(2)} - So ${v.paidShares.so.toFixed(2)} - WT ${v.paidShares.weekday.toFixed(2)}
+
+
Bonus:${this.calculator.formatCurrency(v.bonus)}
+
+ `;
+ }
+
+ /**
+ * Handle vacation checkbox toggle.
+ */
+ onVacationToggle(e) {
+ const cb = e.target;
+ const name = cb.getAttribute('data-vacation-employee');
+ const ym = cb.getAttribute('data-vacation-yearmonth');
+ try {
+ this.storage.setVacationMode(name, ym, cb.checked);
+ // Re-run calc to reflect the new state
+ this.calculateBonuses();
+ } catch (err) {
+ this.showToast('Urlaubsmodus konnte nicht gespeichert werden', 'error');
+ cb.checked = !cb.checked; // revert visual state
+ }
+ }
+
// --- NEW: EMAIL REPORT GENERATOR ---
generateEmailReport() {
// Need to grab current selected calc month/year
diff --git a/styles.css b/styles.css
index 86c29b8..706eda2 100644
--- a/styles.css
+++ b/styles.css
@@ -536,3 +536,125 @@ header h1 {
padding: 20px 0;
}
}
+
+/* === Variants UI === */
+.result-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+.vacation-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ background: #fff;
+ border: 2px solid #e0e0e0;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ cursor: pointer;
+ user-select: none;
+}
+
+.vacation-toggle input[type="checkbox"] {
+ margin: 0;
+ cursor: pointer;
+}
+
+.vacation-active-banner {
+ background: #fff3cd;
+ border-left: 4px solid #ffc107;
+ padding: 8px 12px;
+ border-radius: 4px;
+ margin-bottom: 12px;
+ color: #856404;
+ font-size: 0.9rem;
+}
+
+.classified-summary {
+ display: flex;
+ gap: 20px;
+ flex-wrap: wrap;
+ padding: 10px 15px;
+ background: #f8f9fa;
+ border-radius: 6px;
+ margin: 12px 0;
+ font-size: 0.9rem;
+}
+
+.variant-details {
+ margin-top: 15px;
+ background: #f8f9fa;
+ border-radius: 6px;
+ padding: 10px 15px;
+}
+
+.variant-details summary {
+ cursor: pointer;
+ font-weight: 500;
+ color: #667eea;
+ padding: 4px 0;
+}
+
+.variant-card {
+ background: white;
+ border: 1px solid #e0e0e0;
+ border-radius: 6px;
+ padding: 12px 15px;
+ margin: 10px 0;
+}
+
+.variant-card.winner {
+ border-color: #28a745;
+ box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.15);
+}
+
+.variant-header {
+ margin-bottom: 8px;
+ font-size: 0.95rem;
+}
+
+.variant-badge {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 12px;
+ font-size: 0.75rem;
+ margin-right: 6px;
+ background: #28a745;
+ color: white;
+ font-weight: 600;
+}
+
+.variant-row {
+ display: flex;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 4px 0;
+ font-size: 0.85rem;
+ color: #555;
+ border-top: 1px solid #f0f0f0;
+}
+
+.variant-row:first-of-type {
+ border-top: none;
+}
+
+.variant-bonus {
+ font-weight: 600;
+ color: #333;
+ font-size: 0.95rem;
+}
+
+.variant-eligible {
+ color: #28a745;
+ font-weight: 600;
+}
+
+.variant-not-eligible {
+ color: #dc3545;
+ font-weight: 600;
+}