feat: add date-stepper buttons (Feature C) clamped to selected month

This commit is contained in:
Kenearos 2026-05-12 18:27:06 +02:00
parent ba219ce0eb
commit 016ce93979
3 changed files with 120 additions and 3 deletions

91
app.js
View file

@ -43,8 +43,13 @@ class DienstplanApp {
// Duty management // Duty management
document.getElementById('add-duty-btn').addEventListener('click', () => this.addDuty()); document.getElementById('add-duty-btn').addEventListener('click', () => this.addDuty());
document.getElementById('employee-select-duty').addEventListener('change', () => this.loadDutiesForSelectedEmployee()); document.getElementById('employee-select-duty').addEventListener('change', () => this.loadDutiesForSelectedEmployee());
document.getElementById('month-select').addEventListener('change', () => this.loadDutiesForSelectedEmployee());
document.getElementById('year-select').addEventListener('change', () => this.loadDutiesForSelectedEmployee()); // Date stepper buttons (Feature C)
document.getElementById('duty-date-prev').addEventListener('click', () => this.stepDutyDate(-1));
document.getElementById('duty-date-next').addEventListener('click', () => this.stepDutyDate(+1));
document.getElementById('duty-date').addEventListener('change', () => this.updateDateStepperState());
document.getElementById('month-select').addEventListener('change', () => this.onDutyMonthChange());
document.getElementById('year-select').addEventListener('change', () => this.onDutyMonthChange());
// Calculation // Calculation
document.getElementById('calculate-btn').addEventListener('click', () => this.calculateBonuses()); document.getElementById('calculate-btn').addEventListener('click', () => this.calculateBonuses());
@ -104,6 +109,8 @@ class DienstplanApp {
// Set date input to today // Set date input to today
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
document.getElementById('duty-date').value = today; document.getElementById('duty-date').value = today;
this.updateDateStepperState();
} }
/** /**
@ -265,6 +272,86 @@ class DienstplanApp {
this.loadDutiesForSelectedEmployee(); this.loadDutiesForSelectedEmployee();
} }
/**
* Step the duty-date input by +/-1 day, clamped to the currently selected month.
*/
stepDutyDate(delta) {
const dateInput = document.getElementById('duty-date');
const monthSelect = document.getElementById('month-select');
const yearSelect = document.getElementById('year-select');
const month = parseInt(monthSelect.value);
const year = parseInt(yearSelect.value);
const lastDay = new Date(year, month, 0).getDate();
if (!dateInput.value) {
// Initialize to 1st of the selected month
dateInput.value = `${year}-${String(month).padStart(2, '0')}-01`;
this.updateDateStepperState();
return;
}
const cur = new Date(dateInput.value + 'T12:00:00');
// If outside selected month, snap to 1st
const inMonth = (cur.getFullYear() === year) && ((cur.getMonth() + 1) === month);
if (!inMonth) {
dateInput.value = `${year}-${String(month).padStart(2, '0')}-01`;
this.updateDateStepperState();
return;
}
const curDay = cur.getDate();
const newDay = curDay + delta;
if (newDay < 1 || newDay > lastDay) return; // clamp
const newDate = new Date(year, month - 1, newDay, 12, 0, 0);
const yyyy = newDate.getFullYear();
const mm = String(newDate.getMonth() + 1).padStart(2, '0');
const dd = String(newDate.getDate()).padStart(2, '0');
dateInput.value = `${yyyy}-${mm}-${dd}`;
this.updateDateStepperState();
}
/**
* Update the disabled state of the stepper buttons based on current date / month.
*/
updateDateStepperState() {
const dateInput = document.getElementById('duty-date');
const monthSelect = document.getElementById('month-select');
const yearSelect = document.getElementById('year-select');
const prevBtn = document.getElementById('duty-date-prev');
const nextBtn = document.getElementById('duty-date-next');
if (!dateInput || !prevBtn || !nextBtn) return;
const month = parseInt(monthSelect.value);
const year = parseInt(yearSelect.value);
const lastDay = new Date(year, month, 0).getDate();
if (!dateInput.value) {
prevBtn.disabled = false;
nextBtn.disabled = false;
return;
}
const cur = new Date(dateInput.value + 'T12:00:00');
const inSelectedMonth = (cur.getFullYear() === year) && ((cur.getMonth() + 1) === month);
if (!inSelectedMonth) {
prevBtn.disabled = false;
nextBtn.disabled = false;
return;
}
prevBtn.disabled = cur.getDate() <= 1;
nextBtn.disabled = cur.getDate() >= lastDay;
}
/**
* Handle month/year change in the duty tab: set date to 1st of new month, refresh list, refresh stepper.
*/
onDutyMonthChange() {
const monthSelect = document.getElementById('month-select');
const yearSelect = document.getElementById('year-select');
const month = parseInt(monthSelect.value);
const year = parseInt(yearSelect.value);
document.getElementById('duty-date').value = `${year}-${String(month).padStart(2, '0')}-01`;
this.updateDateStepperState();
this.loadDutiesForSelectedEmployee();
}
/** /**
* Load duties for the selected employee and month * Load duties for the selected employee and month
*/ */

View file

@ -69,7 +69,11 @@
<!-- Add Duty Form --> <!-- Add Duty Form -->
<div class="form-group"> <div class="form-group">
<label for="duty-date">Datum:</label> <label for="duty-date">Datum:</label>
<input type="date" id="duty-date"> <div class="date-stepper">
<button type="button" id="duty-date-prev" class="btn btn-secondary" aria-label="Vorheriger Tag">&lsaquo;</button>
<input type="date" id="duty-date">
<button type="button" id="duty-date-next" class="btn btn-secondary" aria-label="Nächster Tag">&rsaquo;</button>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">

View file

@ -658,3 +658,29 @@ header h1 {
color: #dc3545; color: #dc3545;
font-weight: 600; font-weight: 600;
} }
/* === Date Stepper === */
.date-stepper {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 6px;
align-items: stretch;
}
.date-stepper input[type="date"] {
/* override the .form-group width */
width: 100%;
}
.date-stepper button {
padding: 0 14px;
margin: 0;
font-size: 1.2rem;
line-height: 1;
min-width: 44px;
}
.date-stepper button:disabled {
opacity: 0.4;
cursor: not-allowed;
}