diff --git a/src/app.html b/src/app.html index 9558874..0df2862 100644 --- a/src/app.html +++ b/src/app.html @@ -3,15 +3,25 @@ - + + Stretch My Time Off - Optimise Your Vacation Days + + + + + + + + + %sveltekit.head% - -
-
{new Date(year, month).toLocaleString('default', { month: 'long' })}
- {#each Array.from({ length: firstDay }) as _} -
- {/each} - {#each Array.from({ length: daysInMonth }, (_, i) => i + 1) as day} -
- {day} - {#if getHoliday(day)} - - {/if} -
- {/each} -
\ No newline at end of file + .consecutive-days-off ul { + list-style: none; + padding: 0; + margin: 0; + } + .consecutive-days-off li { + font-size: 0.9em; + } + \ No newline at end of file diff --git a/src/lib/Tooltip.svelte b/src/lib/Tooltip.svelte index 7f1b820..51d020d 100644 --- a/src/lib/Tooltip.svelte +++ b/src/lib/Tooltip.svelte @@ -9,7 +9,7 @@ color: #fff; padding: 5px; border-radius: 3px; - font-size: 0.8em; + font-size: 1.2em; white-space: nowrap; z-index: 1000; pointer-events: none; diff --git a/src/lib/holidayUtils.js b/src/lib/holidayUtils.js index 5a0cdf7..c77180f 100644 --- a/src/lib/holidayUtils.js +++ b/src/lib/holidayUtils.js @@ -41,20 +41,51 @@ export function getHolidaysForYear(countryCode, year) { // Optimize days off to create the longest possible chains export function optimizeDaysOff(holidays, year, daysOff) { + // Filter holidays to include only those in the current year + const currentYearHolidays = holidays.filter(h => h.date.getFullYear() === year); + + // Recalculate weekends for the current year const weekends = getWeekends(year); - const allDaysOffSet = new Set([...holidays.map(h => dateKey(h.date)), ...weekends.map(d => dateKey(d))]); + + // Initialize a new Set for all days off + const allDaysOffSet = new Set([ + ...currentYearHolidays.map(h => dateKey(h.date)), + ...weekends.map(d => dateKey(d)) + ]); let rankedGaps = rankGapsByEfficiency(findGaps(allDaysOffSet, year), allDaysOffSet); - return selectDaysOff(rankedGaps, daysOff, allDaysOffSet); + return selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year); } // Calculate consecutive days off export function calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year) { - const allDays = [...holidays.map(h => h.date), ...optimizedDaysOff]; - allDays.sort((a, b) => a - b); + const allDaysOffSet = new Set([...holidays.map(h => dateKey(h.date)), ...optimizedDaysOff.map(d => dateKey(d))]); + const consecutiveDaysOff = []; + let currentGroup = []; + + for (let month = 0; month < 12; month++) { + for (let day = 1; day <= 31; day++) { + const date = new Date(year, month, day); + if (date.getMonth() !== month) break; // Skip invalid dates - return findConsecutiveDaysOff(allDays, holidays, optimizedDaysOff); + if (isWeekend(date) || isHoliday(date, holidays) || isDayOff(date, allDaysOffSet)) { + currentGroup.push(date); + } else { + if (currentGroup.length > 2) { + addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff); + } + currentGroup = []; + } + } + } + + // Check the last group at the end of the year + if (currentGroup.length > 2) { + addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff); + } + + return consecutiveDaysOff; } // Get all weekends for a specific year @@ -143,7 +174,7 @@ function calculateChain(startDate, gapLength, allDaysOffSet, direction) { } // Select days off based on ranked gaps -function selectDaysOff(rankedGaps, daysOff, allDaysOffSet) { +function selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year) { const selectedDays = []; while (daysOff > 0 && rankedGaps.length > 0) { @@ -165,51 +196,26 @@ function selectDaysOff(rankedGaps, daysOff, allDaysOffSet) { } // Recalculate gaps and re-rank them after each assignment - const newGaps = findGaps(allDaysOffSet, new Date().getFullYear()); + const newGaps = findGaps(allDaysOffSet, year); rankedGaps = rankGapsByEfficiency(newGaps, allDaysOffSet); } return selectedDays; } -// Find consecutive days off -function findConsecutiveDaysOff(allDays, holidays, optimizedDaysOff) { - let consecutiveDaysOff = []; - let currentGroup = []; - let includesHoliday = false; - - allDays.forEach(date => { - if (isWeekend(date) || isHoliday(date, holidays) || isDayOff(date, new Set(optimizedDaysOff.map(d => dateKey(d))))) { - currentGroup.push(date); - if (isHoliday(date, holidays)) includesHoliday = true; - } else if (currentGroup.length > 0) { - addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff, includesHoliday); - currentGroup = []; - includesHoliday = false; - } - }); - - if (currentGroup.length > 0) { - addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff, includesHoliday); - } - - return consecutiveDaysOff; -} - // Add consecutive days off to the list -function addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff, includesHoliday) { - if (currentGroup.some(d => isDayOff(d, new Set(optimizedDaysOff.map(d => dateKey(d)))))) { - const startDate = currentGroup[0]; - const endDate = currentGroup[currentGroup.length - 1]; - const totalDays = daysBetween(startDate, endDate) + 1; - const usedDaysOff = currentGroup.filter(d => isDayOff(d, new Set(optimizedDaysOff.map(d => dateKey(d))))).length; - const message = `${usedDaysOff} days off -> ${totalDays} days`; +function addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff) { + const startDate = currentGroup[0]; + const endDate = currentGroup[currentGroup.length - 1]; + const totalDays = daysBetween(startDate, endDate) + 1; + const usedDaysOff = currentGroup.filter(d => isDayOff(d, new Set(optimizedDaysOff.map(d => dateKey(d))))).length; + if (totalDays > 2) { consecutiveDaysOff.push({ - startDate: formatDate(startDate), - endDate: formatDate(endDate), - includesHoliday, - message + startDate, + endDate, + usedDaysOff, + totalDays }); } } \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ec5e08a..0c96adc 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -17,14 +17,8 @@ let optimizedDaysOff = []; let consecutiveDaysOff = []; let placeholder = "Country"; - let isFirstClick = true; let inputElement; - function handleYearChange(event) { - year = parseInt(event.target.value); - updateHolidays(); - } - function handleCountryChange(event) { const fullValue = event.target.value; const countryCode = Object.keys(countriesList).find(code => countriesList[code] === fullValue); @@ -43,7 +37,7 @@ tempSpan.style.whiteSpace = 'nowrap'; tempSpan.textContent = inputElement.value || inputElement.placeholder; document.body.appendChild(tempSpan); - inputElement.style.width = `${tempSpan.offsetWidth + 30}px`; + inputElement.style.width = `${tempSpan.offsetWidth + 50}px`; document.body.removeChild(tempSpan); } @@ -53,34 +47,15 @@ holidays = getHolidaysForYear(countryCode, year); optimizedDaysOff = optimizeDaysOff(holidays, year, daysOff); consecutiveDaysOff = calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year); + } else { + holidays = []; + optimizedDaysOff = []; + consecutiveDaysOff = []; } - } - - function handleKeyDown(event) { - switch (event.key) { - case 'ArrowRight': - event.preventDefault(); - year++; - updateHolidays(); - break; - case 'ArrowLeft': - event.preventDefault(); - year--; - updateHolidays(); - break; - case 'ArrowUp': - event.preventDefault(); - daysOff++; - updateHolidays(); - break; - case 'ArrowDown': - event.preventDefault(); - if (daysOff > 0) { - daysOff--; - updateHolidays(); - } - break; - } + console.log('Year:', year); + console.log('Holidays updated:', holidays); + console.log('Optimized Days Off:', optimizedDaysOff); + console.log('Consecutive Days Off:', consecutiveDaysOff); } function getFlagEmoji(countryCode) { @@ -89,81 +64,98 @@ .replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt())); } + function handleKeyDown(event) { + switch (event.key) { + case 'ArrowRight': + event.preventDefault(); + year++; + updateHolidays(); // Recalculate holidays for the new year + break; + case 'ArrowLeft': + event.preventDefault(); + year--; + updateHolidays(); // Recalculate holidays for the new year + break; + case 'ArrowUp': + event.preventDefault(); + daysOff++; + updateHolidays(); // Recalculate holidays with updated days off + break; + case 'ArrowDown': + event.preventDefault(); + if (daysOff > 0) { + daysOff--; + updateHolidays(); // Recalculate holidays with updated days off + } + break; + } + } + onMount(() => { - window.addEventListener('keydown', handleKeyDown); - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; adjustInputWidth(inputElement); - inputElement.addEventListener('input', () => adjustInputWidth(inputElement)); + inputElement.addEventListener('input', () => { + adjustInputWidth(inputElement); + const countryCode = Object.keys(countriesList).find(code => countriesList[code] === inputElement.value); + }); inputElement.addEventListener('focus', () => { inputElement.value = ''; adjustInputWidth(inputElement); }); + window.addEventListener('keydown', handleKeyDown); }); updateHolidays(); + console.log(consecutiveDaysOff); -
-

Stretch My Time Off

-
-
+
+

Stretch My Time Off

+

+ In {selectedCountry}, there are {holidays.length} public holidays in {year}. +
+ Let's stretch your {daysOff} days off to {consecutiveDaysOff.reduce((total, group) => total + group.totalDays, 0)} vacation days. +

+
+

I live in - {getFlagEmoji(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))} - { inputElement.value = ''; adjustInputWidth(); }} on:change={handleCountryChange} aria-label="Select country" /> + {getFlagEmoji(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))} + { inputElement.value = ''; adjustInputWidth(); }} aria-label="Select country" /> and have @@ -319,7 +340,7 @@

-
+
Weekend @@ -334,7 +355,7 @@
{#each months as month}
- +
{/each}
@@ -342,5 +363,5 @@
\ No newline at end of file