Add support for fixed days off in calendar component

- Introduced `fixedDaysOff` prop to `CalendarMonth.svelte` for specifying non-working days.
- Enhanced tooltip functionality to indicate fixed days off.
- Updated day click handling to respect fixed days off.
- Modified styles to visually distinguish fixed days off in the calendar.
- Adjusted `optimizeDaysOff` and `calculateConsecutiveDaysOff` functions to account for fixed days off.
- Added tests to ensure fixed days off are excluded from optimization but included in consecutive periods.
- Implemented UI elements for managing fixed days off in the settings panel.
This commit is contained in:
zachd
2025-11-10 00:59:54 +01:00
parent d09ca4f01f
commit ab42bbb65a
4 changed files with 569 additions and 14 deletions
+102 -4
View File
@@ -17,6 +17,7 @@
let daysOff: number = 0;
let optimizedDaysOff: Date[] = [];
let consecutiveDaysOff: Array<{ startDate: Date; endDate: Date; totalDays: number }> = [];
let fixedDaysOff: Date[] = [];
let showExcludedMonths: boolean = true;
let visibleMonths: number[] = [];
let countriesInput: HTMLInputElement | null = null;
@@ -35,6 +36,7 @@
let showHolidaysList: boolean = false;
let showWeekendSettings: boolean = false;
let showFixedDaysOffList: boolean = false;
let weekendDays: number[] = [];
// Start date state
@@ -48,9 +50,15 @@
updateHolidays();
}
// Reactive: when year changes, load start date for that year
// Reactive: when fixedDaysOff changes, update calculations
$: if (fixedDaysOff) {
updateHolidays();
}
// Reactive: when year changes, load start date and fixed days off for that year
$: if (year !== undefined && year) {
startDate = getStartDate(year);
loadFixedDaysOff(year);
}
// Reactive: when startDate or year changes, update excluded months visibility
@@ -108,6 +116,7 @@
selectedStateCode = '';
}
startDate = getStartDate(year);
loadFixedDaysOff(year);
// showExcludedMonths will be set by reactive statement
updateHolidays();
});
@@ -166,8 +175,8 @@
hidden: isHolidayHidden(holiday)
}));
const visibleHolidays = holidays.filter(h => !h.hidden);
optimizedDaysOff = optimizeDaysOff(visibleHolidays, year, daysOff, weekendDays, startDate);
consecutiveDaysOff = calculateConsecutiveDaysOff(visibleHolidays, optimizedDaysOff, year, weekendDays, startDate);
optimizedDaysOff = optimizeDaysOff(visibleHolidays, year, daysOff, weekendDays, startDate, fixedDaysOff);
consecutiveDaysOff = calculateConsecutiveDaysOff(visibleHolidays, optimizedDaysOff, year, weekendDays, startDate, fixedDaysOff);
} else {
holidays = [];
optimizedDaysOff = [];
@@ -175,6 +184,29 @@
}
}
function toggleFixedDayOff(date: Date) {
// Normalize date to remove time component
const normalizedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const dateKeyStr = dateKey(normalizedDate);
// Check if date is already in fixedDaysOff
const existingIndex = fixedDaysOff.findIndex(d => dateKey(d) === dateKeyStr);
if (existingIndex >= 0) {
// Remove if already exists
fixedDaysOff = fixedDaysOff.filter((_, i) => i !== existingIndex);
} else {
// Add if doesn't exist
fixedDaysOff = [...fixedDaysOff, normalizedDate];
}
// Save to localStorage
saveFixedDaysOff(year);
// Update calculations
updateHolidays();
}
function resetToDefault() {
year = defaultYear;
selectedCountry = defaultCountry;
@@ -337,6 +369,42 @@
return `${year}-${month}-${day}`;
}
// Helper function to create a date key (same as in holidayUtils.ts)
function dateKey(date: Date): string {
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
}
// Load fixed days off for a given year from localStorage
function loadFixedDaysOff(year: number) {
try {
const stored = localStorage.getItem(`fixedDaysOff_${year}`);
if (stored) {
const dateStrings: string[] = JSON.parse(stored);
fixedDaysOff = dateStrings.map(dateStr => {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m, d);
});
} else {
fixedDaysOff = [];
}
} catch (e) {
console.error('Error loading fixed days off:', e);
fixedDaysOff = [];
}
}
// Save fixed days off for a given year to localStorage
function saveFixedDaysOff(year: number) {
try {
const dateStrings = fixedDaysOff.map(date =>
`${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`
);
localStorage.setItem(`fixedDaysOff_${year}`, JSON.stringify(dateStrings));
} catch (e) {
console.error('Error saving fixed days off:', e);
}
}
// Format start date for display
function formatStartDate(date: Date): string {
const today = new Date();
@@ -1088,6 +1156,9 @@
<span class="color-box optimized"></span>
<span>Day Off</span>
</div>
<a href="#" on:click|preventDefault={() => showFixedDaysOffList = !showFixedDaysOffList} class="edit-link">
(edit)
</a>
</div>
<div class="key-item">
<div class="key-label">
@@ -1111,8 +1182,33 @@
</div>
</div>
{#if showHolidaysList || showWeekendSettings}
{#if showHolidaysList || showWeekendSettings || showFixedDaysOffList}
<div class="holidays-list">
{#if showFixedDaysOffList}
<div class="settings-section">
<h3>Fixed Days Off</h3>
{#if fixedDaysOff.length > 0}
<ul>
{#each fixedDaysOff.sort((a, b) => a.getTime() - b.getTime()) as fixedDay}
<li>
<div class="setting-item-label">
<span class="color-box optimized"></span>
<span>{formatDate(fixedDay)}</span>
</div>
<button on:click={() => toggleFixedDayOff(fixedDay)}>
Remove
</button>
</li>
{/each}
</ul>
{:else}
<p style="color: #ccc; text-align: center; padding: 20px;">
Tap on a calendar day below to select it as a fixed day off
</p>
{/if}
</div>
{/if}
{#if showHolidaysList}
<div class="settings-section">
<h3>Public Holidays</h3>
@@ -1180,6 +1276,8 @@
consecutiveDaysOff={consecutiveDaysOff}
selectedCountryCode={selectedCountryCode}
weekendDays={weekendDays}
fixedDaysOff={fixedDaysOff}
onDayClick={toggleFixedDayOff}
/>
</div>
{/each}