Fix most errors and warnings with GPT, switch to .ts etc

This commit is contained in:
Zachary
2024-11-13 22:23:07 +01:00
parent 1b862710d7
commit f22af52e9b
4 changed files with 141 additions and 156 deletions

View File

@@ -1,16 +1,15 @@
<script> <script lang="ts">
import Tooltip from './Tooltip.svelte'; import Tooltip from './Tooltip.svelte';
export let year; export let year: number;
export let month; export let month: number;
export let holidays = []; export let holidays: Array<{ date: Date; name: string; hidden?: boolean }>;
export let optimizedDaysOff = []; export let optimizedDaysOff: Date[];
export let consecutiveDaysOff = []; export let consecutiveDaysOff: Array<{ startDate: Date; endDate: Date; totalDays: number }>;
export let selectedCountryCode; export let selectedCountryCode: string;
// Function to determine the first day of the week based on locale // Function to determine the first day of the week based on locale
function getFirstDayOfWeek(locale) { function getFirstDayOfWeek(locale: string): number {
// Convert 'us' to proper locale format
const normalizedLocale = locale.toLowerCase() === 'us' ? 'en-US' : `en-${locale.toUpperCase()}`; const normalizedLocale = locale.toLowerCase() === 'us' ? 'en-US' : `en-${locale.toUpperCase()}`;
try { try {
@@ -34,15 +33,15 @@
$: firstDayOfWeek = getFirstDayOfWeek(locale); $: firstDayOfWeek = getFirstDayOfWeek(locale);
$: adjustedFirstDay = (getFirstDayOfMonth(year, month) - firstDayOfWeek + 7) % 7; $: adjustedFirstDay = (getFirstDayOfMonth(year, month) - firstDayOfWeek + 7) % 7;
function getDaysInMonth(year, month) { function getDaysInMonth(year: number, month: number): number {
return new Date(year, month + 1, 0).getDate(); return new Date(year, month + 1, 0).getDate();
} }
function getFirstDayOfMonth(year, month) { function getFirstDayOfMonth(year: number, month: number): number {
return new Date(year, month, 1).getDay(); return new Date(year, month, 1).getDay();
} }
function getHoliday(day) { function getHoliday(day: number): { date: Date; name: string; hidden?: boolean } | undefined {
return holidays.find(holiday => return holidays.find(holiday =>
holiday.date.getFullYear() === year && holiday.date.getFullYear() === year &&
holiday.date.getMonth() === month && holiday.date.getMonth() === month &&
@@ -50,7 +49,7 @@
); );
} }
function isOptimizedDayOff(day) { function isOptimizedDayOff(day: number): boolean {
return optimizedDaysOff.some(date => return optimizedDaysOff.some(date =>
date.getFullYear() === year && date.getFullYear() === year &&
date.getMonth() === month && date.getMonth() === month &&
@@ -58,8 +57,7 @@
); );
} }
// Determine the dominant month for each consecutive days off period function getDominantMonth(period: { startDate: Date; endDate: Date }): number {
function getDominantMonth(period) {
const startMonth = period.startDate.getMonth(); const startMonth = period.startDate.getMonth();
const endMonth = period.endDate.getMonth(); const endMonth = period.endDate.getMonth();
@@ -73,7 +71,7 @@
return startDays > endDays ? startMonth : endMonth; return startDays > endDays ? startMonth : endMonth;
} }
function isConsecutiveDayOff(day) { function isConsecutiveDayOff(day: number): boolean {
return consecutiveDaysOff.some(period => { return consecutiveDaysOff.some(period => {
const start = period.startDate; const start = period.startDate;
const end = period.endDate; const end = period.endDate;
@@ -82,26 +80,21 @@
}); });
} }
// Function to determine if a day is a weekend function isWeekend(day: number): boolean {
function isWeekend(day) {
const dayOfWeek = (adjustedFirstDay + day - 1) % 7; const dayOfWeek = (adjustedFirstDay + day - 1) % 7;
// Calculate the indices for Saturday and Sunday
const saturdayIndex = (6 - firstDayOfWeek + 7) % 7; const saturdayIndex = (6 - firstDayOfWeek + 7) % 7;
const sundayIndex = (7 - firstDayOfWeek + 7) % 7; const sundayIndex = (7 - firstDayOfWeek + 7) % 7;
return dayOfWeek === saturdayIndex || dayOfWeek === sundayIndex; return dayOfWeek === saturdayIndex || dayOfWeek === sundayIndex;
} }
// Fixed array of day initials starting from Sunday
const dayInitials = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; const dayInitials = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
// Reactive declaration to get the ordered day initials
$: orderedDayInitials = dayInitials.slice(firstDayOfWeek).concat(dayInitials.slice(0, firstDayOfWeek)); $: orderedDayInitials = dayInitials.slice(firstDayOfWeek).concat(dayInitials.slice(0, firstDayOfWeek));
</script> </script>
<div class="calendar"> <div class="calendar">
<div class="month-name">{new Date(year, month).toLocaleString('default', { month: 'long' })}</div> <div class="month-name">{new Date(year, month).toLocaleString('default', { month: 'long' })}</div>
<!-- Day initials header -->
{#each orderedDayInitials as dayInitial} {#each orderedDayInitials as dayInitial}
<div class="day-initial">{dayInitial}</div> <div class="day-initial">{dayInitial}</div>
{/each} {/each}
@@ -113,7 +106,7 @@
<div class="day {isWeekend(day) ? 'weekend' : ''} {getHoliday(day) ? 'holiday' : ''} {isOptimizedDayOff(day) ? 'optimized' : ''} {isConsecutiveDayOff(day) ? 'consecutive-day' : ''}"> <div class="day {isWeekend(day) ? 'weekend' : ''} {getHoliday(day) ? 'holiday' : ''} {isOptimizedDayOff(day) ? 'optimized' : ''} {isConsecutiveDayOff(day) ? 'consecutive-day' : ''}">
{day} {day}
{#if getHoliday(day)} {#if getHoliday(day)}
<Tooltip text={getHoliday(day).name} /> <Tooltip text={getHoliday(day)?.name} />
{/if} {/if}
</div> </div>
{/each} {/each}

View File

@@ -5,32 +5,32 @@ const MS_IN_A_DAY = 86400000;
const MAX_GAP_LENGTH = 5; const MAX_GAP_LENGTH = 5;
// Helper function to check if a date is a weekend // Helper function to check if a date is a weekend
const isWeekend = date => date.getDay() === 0 || date.getDay() === 6; const isWeekend = (date: Date): boolean => date.getDay() === 0 || date.getDay() === 6;
// Helper function to check if two dates are the same day // Helper function to check if two dates are the same day
const isSameDay = (date1, date2) => const isSameDay = (date1: Date, date2: Date): boolean =>
date1.getFullYear() === date2.getFullYear() && date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() && date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate(); date1.getDate() === date2.getDate();
// Helper function to generate a unique key for a date // Helper function to generate a unique key for a date
const dateKey = date => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; const dateKey = (date: Date): string => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
// Helper function to check if a date is a holiday // Helper function to check if a date is a holiday
const isHoliday = (date, holidays) => holidays.some(h => isSameDay(h.date, date)); const isHoliday = (date: Date, holidays: { date: Date }[]): boolean => holidays.some(h => isSameDay(h.date, date));
// Helper function to check if a date is a day off // Helper function to check if a date is a day off
const isDayOff = (date, allDaysOffSet) => allDaysOffSet.has(dateKey(date)); const isDayOff = (date: Date, allDaysOffSet: Set<string>): boolean => allDaysOffSet.has(dateKey(date));
// Helper function to calculate the number of days between two dates // Helper function to calculate the number of days between two dates
const daysBetween = (startDate, endDate) => Math.round((endDate - startDate) / MS_IN_A_DAY); const daysBetween = (startDate: Date, endDate: Date): number => Math.round((endDate.getTime() - startDate.getTime()) / MS_IN_A_DAY);
// Helper function to format a date // Helper function to format a date
const formatDate = date => date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); const formatDate = (date: Date): string => date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
// Get holidays for a specific year and country // Get holidays for a specific year and country
export function getHolidaysForYear(countryCode, year, stateCode = '') { export function getHolidaysForYear(countryCode: string, year: number, stateCode?: string): { date: Date; name: string }[] {
const hd = new Holidays(countryCode, stateCode); const hd = stateCode ? new Holidays(countryCode, stateCode) : new Holidays(countryCode);
return hd.getHolidays(year) return hd.getHolidays(year)
.filter(holiday => holiday.type === 'public') .filter(holiday => holiday.type === 'public')
.map(holiday => ({ .map(holiday => ({
@@ -40,14 +40,9 @@ export function getHolidaysForYear(countryCode, year, stateCode = '') {
} }
// Optimize days off to create the longest possible chains // Optimize days off to create the longest possible chains
export function optimizeDaysOff(holidays, year, daysOff) { export function optimizeDaysOff(holidays: { date: Date }[], year: number, daysOff: number): Date[] {
// Filter holidays to include only those in the current year
const currentYearHolidays = holidays.filter(h => h.date.getFullYear() === year); const currentYearHolidays = holidays.filter(h => h.date.getFullYear() === year);
// Recalculate weekends for the current year
const weekends = getWeekends(year); const weekends = getWeekends(year);
// Initialize a new Set for all days off
const allDaysOffSet = new Set([ const allDaysOffSet = new Set([
...currentYearHolidays.map(h => dateKey(h.date)), ...currentYearHolidays.map(h => dateKey(h.date)),
...weekends.map(d => dateKey(d)) ...weekends.map(d => dateKey(d))
@@ -59,15 +54,15 @@ export function optimizeDaysOff(holidays, year, daysOff) {
} }
// Calculate consecutive days off // Calculate consecutive days off
export function calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year) { export function calculateConsecutiveDaysOff(holidays: { date: Date }[], optimizedDaysOff: Date[], year: number): { startDate: Date; endDate: Date; usedDaysOff: number; totalDays: number }[] {
const allDaysOffSet = new Set([...holidays.map(h => dateKey(h.date)), ...optimizedDaysOff.map(d => dateKey(d))]); const allDaysOffSet = new Set([...holidays.map(h => dateKey(h.date)), ...optimizedDaysOff.map(d => dateKey(d))]);
const consecutiveDaysOff = []; const consecutiveDaysOff: { startDate: Date; endDate: Date; usedDaysOff: number; totalDays: number }[] = [];
let currentGroup = []; let currentGroup: Date[] = [];
for (let month = 0; month < 12; month++) { for (let month = 0; month < 12; month++) {
for (let day = 1; day <= 31; day++) { for (let day = 1; day <= 31; day++) {
const date = new Date(year, month, day); const date = new Date(year, month, day);
if (date.getMonth() !== month) break; // Skip invalid dates if (date.getMonth() !== month) break;
if (isWeekend(date) || isHoliday(date, holidays) || isDayOff(date, allDaysOffSet)) { if (isWeekend(date) || isHoliday(date, holidays) || isDayOff(date, allDaysOffSet)) {
currentGroup.push(date); currentGroup.push(date);
@@ -80,7 +75,6 @@ export function calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year) {
} }
} }
// Check the last group at the end of the year
if (currentGroup.length > 2) { if (currentGroup.length > 2) {
addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff); addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff);
} }
@@ -89,8 +83,8 @@ export function calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year) {
} }
// Get all weekends for a specific year // Get all weekends for a specific year
function getWeekends(year) { function getWeekends(year: number): Date[] {
const weekends = []; const weekends: Date[] = [];
for (let month = 0; month < 12; month++) { for (let month = 0; month < 12; month++) {
for (let day = 1; day <= 31; day++) { for (let day = 1; day <= 31; day++) {
const date = new Date(year, month, day); const date = new Date(year, month, day);
@@ -102,9 +96,9 @@ function getWeekends(year) {
} }
// Find gaps between days off // Find gaps between days off
function findGaps(allDaysOffSet, year) { function findGaps(allDaysOffSet: Set<string>, year: number): { start: Date; end: Date; gapLength: number }[] {
const gaps = []; const gaps: { start: Date; end: Date; gapLength: number }[] = [];
let currentGapStart = null; let currentGapStart: Date | null = null;
for (let month = 0; month < 12; month++) { for (let month = 0; month < 12; month++) {
for (let day = 1; day <= 31; day++) { for (let day = 1; day <= 31; day++) {
@@ -137,7 +131,7 @@ function findGaps(allDaysOffSet, year) {
} }
// Rank gaps by efficiency // Rank gaps by efficiency
function rankGapsByEfficiency(gaps, allDaysOffSet) { function rankGapsByEfficiency(gaps: { start: Date; end: Date; gapLength: number }[], allDaysOffSet: Set<string>): any[] {
return gaps.map(gap => { return gaps.map(gap => {
const backward = calculateChain(gap.start, gap.gapLength, allDaysOffSet, 'backward'); const backward = calculateChain(gap.start, gap.gapLength, allDaysOffSet, 'backward');
const forward = calculateChain(gap.end, gap.gapLength, allDaysOffSet, 'forward'); const forward = calculateChain(gap.end, gap.gapLength, allDaysOffSet, 'forward');
@@ -149,7 +143,7 @@ function rankGapsByEfficiency(gaps, allDaysOffSet) {
} }
// Calculate potential chain length and days off used // Calculate potential chain length and days off used
function calculateChain(startDate, gapLength, allDaysOffSet, direction) { function calculateChain(startDate: Date, gapLength: number, allDaysOffSet: Set<string>, direction: 'backward' | 'forward'): { chainLength: number; usedDaysOff: number } {
let chainLength = gapLength; let chainLength = gapLength;
let usedDaysOff = 0; let usedDaysOff = 0;
let currentDate = new Date(startDate); let currentDate = new Date(startDate);
@@ -174,13 +168,12 @@ function calculateChain(startDate, gapLength, allDaysOffSet, direction) {
} }
// Select days off based on ranked gaps // Select days off based on ranked gaps
function selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year) { function selectDaysOff(rankedGaps: any[], daysOff: number, allDaysOffSet: Set<string>, year: number): Date[] {
const selectedDays = []; const selectedDays: Date[] = [];
while (daysOff > 0 && rankedGaps.length > 0) { while (daysOff > 0 && rankedGaps.length > 0) {
const gap = rankedGaps.shift(); // Get the highest-ranked gap const gap = rankedGaps.shift();
// Determine the direction and starting point for filling the gap
const increment = gap.fillFrom === 'start' ? 1 : -1; const increment = gap.fillFrom === 'start' ? 1 : -1;
const startDate = gap.fillFrom === 'start' ? gap.start : gap.end; const startDate = gap.fillFrom === 'start' ? gap.start : gap.end;
@@ -195,7 +188,6 @@ function selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year) {
} }
} }
// Recalculate gaps and re-rank them after each assignment
const newGaps = findGaps(allDaysOffSet, year); const newGaps = findGaps(allDaysOffSet, year);
rankedGaps = rankGapsByEfficiency(newGaps, allDaysOffSet); rankedGaps = rankGapsByEfficiency(newGaps, allDaysOffSet);
} }
@@ -204,7 +196,7 @@ function selectDaysOff(rankedGaps, daysOff, allDaysOffSet, year) {
} }
// Add consecutive days off to the list // Add consecutive days off to the list
function addConsecutiveDaysOff(consecutiveDaysOff, currentGroup, optimizedDaysOff) { function addConsecutiveDaysOff(consecutiveDaysOff: { startDate: Date; endDate: Date; usedDaysOff: number; totalDays: number }[], currentGroup: Date[], optimizedDaysOff: Date[]) {
const startDate = currentGroup[0]; const startDate = currentGroup[0];
const endDate = currentGroup[currentGroup.length - 1]; const endDate = currentGroup[currentGroup.length - 1];
const totalDays = daysBetween(startDate, endDate) + 1; const totalDays = daysBetween(startDate, endDate) + 1;

View File

@@ -1,4 +1,4 @@
export const ptoData = { export const ptoData: Record<string, number> = {
// Afghanistan // Afghanistan
AF: 20, // 20 days recreational leave, 15 paid public holidays AF: 20, // 20 days recreational leave, 15 paid public holidays

View File

@@ -1,62 +1,62 @@
<script> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { injectSpeedInsights } from '@vercel/speed-insights'; import { injectSpeedInsights } from '@vercel/speed-insights';
import { inject } from '@vercel/analytics' import { inject } from '@vercel/analytics';
import countries from 'i18n-iso-countries'; import countries from 'i18n-iso-countries';
import enLocale from 'i18n-iso-countries/langs/en.json'; import enLocale from 'i18n-iso-countries/langs/en.json';
import CalendarMonth from '../lib/CalendarMonth.svelte'; import CalendarMonth from '../lib/CalendarMonth.svelte';
import { getHolidaysForYear, optimizeDaysOff, calculateConsecutiveDaysOff } from '../lib/holidayUtils.js'; import { getHolidaysForYear, optimizeDaysOff, calculateConsecutiveDaysOff } from '../lib/holidayUtils';
import { ptoData } from '../lib/ptoData.js'; import { ptoData } from '../lib/ptoData';
import Holidays from 'date-holidays'; import Holidays from 'date-holidays';
countries.registerLocale(enLocale); countries.registerLocale(enLocale);
let countriesList = countries.getNames('en'); let countriesList: Record<string, string> = countries.getNames('en');
let year; let year: number;
let months = Array.from({ length: 12 }, (_, i) => i); let months: number[] = Array.from({ length: 12 }, (_, i) => i);
let selectedCountry = ''; let selectedCountry: string = '';
let holidays = []; let holidays: Array<{ date: Date; name: string; hidden?: boolean }> = [];
let daysOff = 0; let daysOff: number = 0;
let optimizedDaysOff = []; let optimizedDaysOff: Date[] = [];
let consecutiveDaysOff = []; let consecutiveDaysOff: Array<{ startDate: Date; endDate: Date; totalDays: number }> = [];
let placeholder = "Country"; let countriesInput: HTMLInputElement | null = null;
let countriesInput; let statesInput: HTMLInputElement | null = null;
let statesInput; let showHowItWorks: boolean = false;
let showHowItWorks = false;
// Default settings // Default settings
let defaultYear = new Date().getFullYear(); let defaultYear: number = new Date().getFullYear();
let defaultCountry = ''; let defaultCountry: string = '';
let defaultDaysOff = 0; let defaultDaysOff: number = 0;
let selectedState = ''; let selectedState: string = '';
let selectedStateCode = ''; let selectedStateCode: string = '';
let statesList = []; let statesList: Record<string, string> = {};
let showHolidaysList = false; // State to toggle the visibility of the holidays list let showHolidaysList: boolean = false;
$: selectedCountryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry); $: selectedCountryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry) || '';
$: if (selectedCountryCode || selectedStateCode || daysOff || year) { $: if (selectedCountryCode || selectedStateCode || daysOff || year) {
updateHolidays(); updateHolidays();
} }
$: if (daysOff) { $: if (daysOff) {
localStorage.setItem('daysOff', daysOff); localStorage.setItem('daysOff', daysOff.toString());
} }
$: if (year) { $: if (year) {
localStorage.setItem('year', year); localStorage.setItem('year', year.toString());
} }
function updateStatesList(countryCode) { function updateStatesList(countryCode: string) {
const hd = new Holidays(countryCode); const hd = new Holidays(countryCode);
statesList = hd.getStates(countryCode) || []; statesList = hd.getStates(countryCode) || {};
} }
function handleStateChange(event) { function handleStateChange(event: Event) {
const stateName = event.target.value; const target = event.target as HTMLInputElement;
selectedStateCode = Object.keys(statesList).find(code => statesList[code] === stateName); const stateName = target.value;
selectedStateCode = Object.keys(statesList).find(code => statesList[code] === stateName) || '';
selectedState = stateName; selectedState = stateName;
localStorage.setItem('selectedState', selectedState); localStorage.setItem('selectedState', selectedState);
localStorage.setItem('selectedStateCode', selectedStateCode); localStorage.setItem('selectedStateCode', selectedStateCode);
@@ -66,11 +66,10 @@
inject(); inject();
injectSpeedInsights(); injectSpeedInsights();
// Always fetch the real country and PTO data
fetchCountryCode().then(() => { fetchCountryCode().then(() => {
defaultYear = new Date().getFullYear(); defaultYear = new Date().getFullYear();
defaultCountry = selectedCountry; defaultCountry = selectedCountry;
defaultDaysOff = ptoData[selectedCountryCode]; defaultDaysOff = ptoData[selectedCountryCode] || 0;
const storedYear = localStorage.getItem('year'); const storedYear = localStorage.getItem('year');
const storedCountry = localStorage.getItem('selectedCountry'); const storedCountry = localStorage.getItem('selectedCountry');
@@ -104,8 +103,9 @@
} }
} }
function handleCountryChange(event) { function handleCountryChange(event: Event) {
const fullValue = event.target.value; const target = event.target as HTMLInputElement;
const fullValue = target.value;
if (selectedCountryCode) { if (selectedCountryCode) {
daysOff = ptoData[selectedCountryCode] || 0; daysOff = ptoData[selectedCountryCode] || 0;
selectedState = ''; // Reset state selectedState = ''; // Reset state
@@ -114,7 +114,7 @@
localStorage.setItem('selectedCountry', selectedCountry); localStorage.setItem('selectedCountry', selectedCountry);
localStorage.setItem('selectedState', selectedState); localStorage.setItem('selectedState', selectedState);
localStorage.setItem('selectedStateCode', selectedStateCode); localStorage.setItem('selectedStateCode', selectedStateCode);
localStorage.setItem('daysOff', daysOff); localStorage.setItem('daysOff', daysOff.toString());
} }
} }
@@ -124,9 +124,11 @@
let allHolidays = getHolidaysForYear(selectedCountryCode, year, selectedStateCode); let allHolidays = getHolidaysForYear(selectedCountryCode, year, selectedStateCode);
holidays = allHolidays.map(holiday => ({ holidays = allHolidays.map(holiday => ({
...holiday, ...holiday,
date: new Date(holiday.date),
hidden: isHolidayHidden(holiday) hidden: isHolidayHidden(holiday)
})); }));
const visibleHolidays = holidays.filter(h => !h.hidden); const visibleHolidays = holidays
.filter(h => !h.hidden);
optimizedDaysOff = optimizeDaysOff(visibleHolidays, year, daysOff); optimizedDaysOff = optimizeDaysOff(visibleHolidays, year, daysOff);
consecutiveDaysOff = calculateConsecutiveDaysOff(visibleHolidays, optimizedDaysOff, year); consecutiveDaysOff = calculateConsecutiveDaysOff(visibleHolidays, optimizedDaysOff, year);
} else { } else {
@@ -142,14 +144,14 @@
selectedState = ''; selectedState = '';
selectedStateCode = ''; selectedStateCode = '';
daysOff = defaultDaysOff; daysOff = defaultDaysOff;
localStorage.setItem('year', year); localStorage.setItem('year', year.toString());
localStorage.setItem('selectedCountry', selectedCountry); localStorage.setItem('selectedCountry', selectedCountry);
localStorage.setItem('selectedState', selectedState); localStorage.setItem('selectedState', selectedState);
localStorage.setItem('selectedStateCode', selectedStateCode); localStorage.setItem('selectedStateCode', selectedStateCode);
localStorage.setItem('daysOff', daysOff); localStorage.setItem('daysOff', daysOff.toString());
} }
function handleKeyDown(event) { function handleKeyDown(event: KeyboardEvent) {
switch (event.key) { switch (event.key) {
case 'ArrowRight': case 'ArrowRight':
event.preventDefault(); event.preventDefault();
@@ -176,8 +178,8 @@
} }
} }
function adjustInputWidth(inputElement, value) { function adjustInputWidth(inputElement: HTMLInputElement | null, value: string) {
if (typeof window !== 'undefined' && inputElement) { // Ensure this runs only in the browser if (typeof window !== 'undefined' && inputElement) {
const tempSpan = document.createElement('span'); const tempSpan = document.createElement('span');
tempSpan.style.visibility = 'hidden'; tempSpan.style.visibility = 'hidden';
tempSpan.style.position = 'absolute'; tempSpan.style.position = 'absolute';
@@ -189,15 +191,14 @@
} }
} }
// Reactive statements to adjust input width when values change
$: if (countriesInput) adjustInputWidth(countriesInput, selectedCountry); $: if (countriesInput) adjustInputWidth(countriesInput, selectedCountry);
$: if (statesInput) adjustInputWidth(statesInput, selectedState); $: if (statesInput) adjustInputWidth(statesInput, selectedState);
function getFlagEmoji(countryCode) { function getFlagEmoji(countryCode: string) {
if (!countryCode) return ''; // Return an empty string if countryCode is not available if (!countryCode) return '';
return countryCode return countryCode
.toUpperCase() .toUpperCase()
.replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt())); .replace(/./g, char => String.fromCodePoint(127397 + char.charCodeAt(0)));
} }
function toggleHowItWorks() { function toggleHowItWorks() {
@@ -210,48 +211,39 @@
} }
} }
// Function to toggle the visibility of a holiday function toggleHolidayVisibility(holiday: { date: Date; name: string; hidden?: boolean }) {
function toggleHolidayVisibility(holiday) { if (!selectedCountryCode) return;
const countryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry);
if (!countryCode) return;
const storageKey = `hiddenHolidays_${countryCode}`; const storageKey = `hiddenHolidays_${selectedCountryCode}`;
let hiddenHolidays = JSON.parse(localStorage.getItem(storageKey)) || {}; let hiddenHolidays = JSON.parse(localStorage.getItem(storageKey) || '{}');
// Toggle the hidden state of the holiday hiddenHolidays[holiday.date.toString()] = !hiddenHolidays[holiday.date.toString()];
hiddenHolidays[holiday.date] = !hiddenHolidays[holiday.date];
localStorage.setItem(storageKey, JSON.stringify(hiddenHolidays)); localStorage.setItem(storageKey, JSON.stringify(hiddenHolidays));
// Update the holidays list to trigger reactivity
holidays = holidays.map(h => { holidays = holidays.map(h => {
if (h.date === holiday.date) { if (h.date === holiday.date) {
return { ...h, hidden: hiddenHolidays[h.date] }; return { ...h, hidden: hiddenHolidays[h.date.toString()] };
} }
return h; return h;
}); });
// Recalculate holidays to ensure hidden ones are excluded
updateHolidays(); updateHolidays();
} }
// Function to check if a holiday is hidden function isHolidayHidden(holiday: { date: Date; name: string; hidden?: boolean }) {
function isHolidayHidden(holiday) { if (!selectedCountryCode) return false;
const countryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry);
if (!countryCode) return false;
const storageKey = `hiddenHolidays_${countryCode}`; const storageKey = `hiddenHolidays_${selectedCountryCode}`;
const hiddenHolidays = JSON.parse(localStorage.getItem(storageKey)) || {}; const hiddenHolidays = JSON.parse(localStorage.getItem(storageKey) || '{}');
return hiddenHolidays[holiday.date] || false; return hiddenHolidays[holiday.date.toString()] || false;
} }
// Function to format the date function formatDate(date: Date) {
function formatDate(dateString) { const options: Intl.DateTimeFormatOptions = { day: '2-digit', month: 'short', year: 'numeric' };
const options = { day: '2-digit', month: 'short', year: 'numeric' }; return new Date(date).toLocaleDateString('en-GB', options);
return new Date(dateString).toLocaleDateString('en-GB', options);
} }
// Reactive statement to calculate the number of visible holidays
$: visibleHolidaysCount = holidays.filter(h => !h.hidden).length; $: visibleHolidaysCount = holidays.filter(h => !h.hidden).length;
</script> </script>
@@ -321,7 +313,7 @@
@media (max-width: 600px) { @media (max-width: 600px) {
.calendar-grid { .calendar-grid {
grid-template-columns: repeat(2, 1fr); /* Allow 2 columns on smaller screens */ grid-template-columns: repeat(2, 1fr);
gap: 5px; gap: 5px;
padding: 5px; padding: 5px;
} }
@@ -400,23 +392,23 @@
button { button {
background-color: #333; background-color: #333;
border-left: 4px solid #111; /* Lighter border on the left */ border-left: 4px solid #111;
border-top: 4px solid #111; /* Lighter border on the top */ border-top: 4px solid #111;
border-right: 4px solid #555; /* Darker border on the right */ border-right: 4px solid #555;
border-bottom: 4px solid #555; /* Darker border on the bottom */ border-bottom: 4px solid #555;
color: #fff; color: #fff;
font-size: 0.8em; /* Smaller font size */ font-size: 0.8em;
cursor: pointer; cursor: pointer;
padding: 3px; /* Reduced padding */ padding: 3px;
margin: 0 3px; /* Reduced margin */ margin: 0 3px;
border-radius: 4px; /* Slightly less rounded edges */ border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transition: transform 0.1s; transition: transform 0.1s;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 30px; /* Smaller width */ width: 30px;
height: 30px; /* Smaller height */ height: 30px;
font-weight: bold; font-weight: bold;
} }
@@ -443,18 +435,6 @@
font-size: 1.5em; font-size: 1.5em;
} }
.day {
aspect-ratio: 1;
text-align: center;
font-size: 0.6em;
display: flex;
align-items: center;
justify-content: center;
color: #ddd;
background-color: #222;
position: relative;
}
.how-it-works { .how-it-works {
margin: 20px auto; margin: 20px auto;
padding: 25px; padding: 25px;
@@ -482,10 +462,22 @@
text-align: center; text-align: center;
font-size: 1em; font-size: 1em;
transition: color 0.3s; transition: color 0.3s;
display: block;
width: auto;
background: none;
border: none;
padding: 0;
} }
.toggle-text:hover { .toggle-text:hover {
color: #61dafb; color: #61dafb;
background: none;
border: none;
padding: 0;
}
.toggle-text:focus {
outline: none;
} }
.reset-link { .reset-link {
@@ -579,7 +571,7 @@
<p> <p>
In In
<strong> <strong>
{getFlagEmoji(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))} {getFlagEmoji(selectedCountryCode)}
{#if selectedState} {#if selectedState}
{selectedState}, {selectedState},
{/if} {/if}
@@ -594,9 +586,13 @@
<div class="content-box"> <div class="content-box">
<p> <p>
I live in I live in
<span class="flag" style="vertical-align: middle;">{getFlagEmoji(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))}</span> <span class="flag" style="vertical-align: middle;">{getFlagEmoji(selectedCountryCode)}</span>
{#if Object.keys(statesList).length > 0} {#if Object.keys(statesList).length > 0}
<input bind:this={statesInput} list="states" class="editable-input bold" bind:value={selectedState} on:input={(e) => { handleStateChange(e); }} on:focus={() => { statesInput.value = ''; }} placeholder="State" aria-label="State" /> <input bind:this={statesInput} list="states" class="editable-input bold" bind:value={selectedState}
on:input={(e) => handleStateChange(e)}
on:focus={(e) => { (e.target as HTMLInputElement).value = ''; }}
placeholder="State"
aria-label="State" />
<datalist id="states"> <datalist id="states">
{#each Object.entries(statesList) as [code, name]} {#each Object.entries(statesList) as [code, name]}
<option value={name}>{name}</option> <option value={name}>{name}</option>
@@ -604,7 +600,11 @@
</datalist> </datalist>
in in
{/if} {/if}
<input bind:this={countriesInput} list="countries" class="editable-input bold" bind:value={selectedCountry} placeholder={placeholder} on:input={(e) => { handleCountryChange(e); }} on:focus={() => { countriesInput.value = ''; }} aria-label="Select country" /> <input bind:this={countriesInput} list="countries" class="editable-input bold" bind:value={selectedCountry}
on:input={(e) => handleCountryChange(e)}
on:focus={(e) => { (e.target as HTMLInputElement).value = ''; }}
placeholder="Country"
aria-label="Select country" />
and have and have
<span class="arrow-controls"> <span class="arrow-controls">
<button on:click={() => { if (daysOff > 0) { daysOff--; updateHolidays(); } }} aria-label="Decrease days off"></button> <button on:click={() => { if (daysOff > 0) { daysOff--; updateHolidays(); } }} aria-label="Decrease days off"></button>
@@ -682,15 +682,15 @@
</div> </div>
</div> </div>
<div class="toggle-text" on:click={toggleHowItWorks}> <button type="button" class="toggle-text" on:click={toggleHowItWorks}>
{showHowItWorks ? 'Hide Details' : 'How does this work?'} {showHowItWorks ? 'Hide Details' : 'How does this work?'}
</div> </button>
{#if showHowItWorks} {#if showHowItWorks}
<div id="how-it-works" class="content-box how-it-works"> <div id="how-it-works" class="content-box how-it-works">
<h3>How does this work?</h3> <h3>How does this work?</h3>
<p> <p>
This tool detects your country from your IP, uses a default number of government-mandated days off from <a href="https://en.wikipedia.org/wiki/List_of_minimum_annual_leave_by_country" target="_blank" rel="noopener noreferrer">Wikipedia</a>, and a <a href={`https://github.com/commenthol/date-holidays/blob/master/data/countries/${Object.keys(countriesList).find(code => countriesList[code] === selectedCountry)}.yaml`} target="_blank" rel="noopener noreferrer">list of holidays</a> for {selectedCountry}. This tool detects your country from your IP, uses a default number of government-mandated days off from <a href="https://en.wikipedia.org/wiki/List_of_minimum_annual_leave_by_country" target="_blank" rel="noopener noreferrer">Wikipedia</a>, and a <a href={`https://github.com/commenthol/date-holidays/blob/master/data/countries/${selectedCountryCode}.yaml`} target="_blank" rel="noopener noreferrer">list of holidays</a> for {selectedCountry}.
</p> </p>
<p> <p>
The algorithm prioritizes filling the shortest gaps first. It optimizes for spreading your holidays throughout the year to create the most number of consecutive vacation periods. The algorithm prioritizes filling the shortest gaps first. It optimizes for spreading your holidays throughout the year to create the most number of consecutive vacation periods.