Add state selection
This commit is contained in:
@@ -29,8 +29,8 @@ const daysBetween = (startDate, endDate) => Math.round((endDate - startDate) / M
|
|||||||
const formatDate = date => date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
const formatDate = date => 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) {
|
export function getHolidaysForYear(countryCode, year, stateCode = '') {
|
||||||
const hd = new Holidays(countryCode);
|
const hd = new Holidays(countryCode, stateCode);
|
||||||
return hd.getHolidays(year)
|
return hd.getHolidays(year)
|
||||||
.filter(holiday => holiday.type === 'public')
|
.filter(holiday => holiday.type === 'public')
|
||||||
.map(holiday => ({
|
.map(holiday => ({
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
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.js';
|
||||||
import { ptoData } from '../lib/ptoData.js';
|
import { ptoData } from '../lib/ptoData.js';
|
||||||
|
import Holidays from 'date-holidays';
|
||||||
|
|
||||||
countries.registerLocale(enLocale);
|
countries.registerLocale(enLocale);
|
||||||
let countriesList = countries.getNames('en');
|
let countriesList = countries.getNames('en');
|
||||||
@@ -19,7 +20,8 @@
|
|||||||
let optimizedDaysOff = [];
|
let optimizedDaysOff = [];
|
||||||
let consecutiveDaysOff = [];
|
let consecutiveDaysOff = [];
|
||||||
let placeholder = "Country";
|
let placeholder = "Country";
|
||||||
let inputElement;
|
let countriesInput;
|
||||||
|
let statesInput;
|
||||||
let showHowItWorks = false;
|
let showHowItWorks = false;
|
||||||
|
|
||||||
// Default settings
|
// Default settings
|
||||||
@@ -27,6 +29,24 @@
|
|||||||
let defaultCountry = '';
|
let defaultCountry = '';
|
||||||
let defaultDaysOff = 0;
|
let defaultDaysOff = 0;
|
||||||
|
|
||||||
|
let selectedState = '';
|
||||||
|
let selectedStateCode = '';
|
||||||
|
let statesList = [];
|
||||||
|
|
||||||
|
function updateStatesList(countryCode) {
|
||||||
|
const hd = new Holidays(countryCode);
|
||||||
|
statesList = hd.getStates(countryCode) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStateChange(event) {
|
||||||
|
const stateName = event.target.value;
|
||||||
|
selectedStateCode = Object.keys(statesList).find(code => statesList[code] === stateName);
|
||||||
|
selectedState = stateName;
|
||||||
|
updateHolidays();
|
||||||
|
localStorage.setItem('selectedState', selectedState);
|
||||||
|
localStorage.setItem('selectedStateCode', selectedStateCode);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
inject();
|
inject();
|
||||||
injectSpeedInsights();
|
injectSpeedInsights();
|
||||||
@@ -40,23 +60,25 @@
|
|||||||
const storedYear = localStorage.getItem('year');
|
const storedYear = localStorage.getItem('year');
|
||||||
const storedCountry = localStorage.getItem('selectedCountry');
|
const storedCountry = localStorage.getItem('selectedCountry');
|
||||||
const storedDaysOff = localStorage.getItem('daysOff');
|
const storedDaysOff = localStorage.getItem('daysOff');
|
||||||
|
const storedState = localStorage.getItem('selectedState');
|
||||||
|
const storedStateCode = localStorage.getItem('selectedStateCode');
|
||||||
|
|
||||||
year = storedYear ? parseInt(storedYear, 10) : defaultYear;
|
year = storedYear ? parseInt(storedYear, 10) : defaultYear;
|
||||||
selectedCountry = storedCountry || defaultCountry;
|
selectedCountry = storedCountry || defaultCountry;
|
||||||
daysOff = storedDaysOff ? parseInt(storedDaysOff, 10) : defaultDaysOff;
|
daysOff = storedDaysOff ? parseInt(storedDaysOff, 10) : defaultDaysOff;
|
||||||
|
selectedState = storedState || '';
|
||||||
|
selectedStateCode = storedStateCode || '';
|
||||||
|
|
||||||
|
const countryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry);
|
||||||
|
updateStatesList(countryCode);
|
||||||
|
|
||||||
updateHolidays();
|
updateHolidays();
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const countryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry);
|
||||||
|
if (countryCode) {
|
||||||
|
updateStatesList(countryCode);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchCountryCode() {
|
async function fetchCountryCode() {
|
||||||
@@ -78,17 +100,21 @@
|
|||||||
if (countryCode) {
|
if (countryCode) {
|
||||||
selectedCountry = fullValue;
|
selectedCountry = fullValue;
|
||||||
daysOff = ptoData[countryCode] || 0;
|
daysOff = ptoData[countryCode] || 0;
|
||||||
|
selectedState = ''; // Reset state
|
||||||
|
selectedStateCode = ''; // Reset state code
|
||||||
|
updateStatesList(countryCode); // Update states list for the new country
|
||||||
updateHolidays();
|
updateHolidays();
|
||||||
localStorage.setItem('selectedCountry', selectedCountry);
|
localStorage.setItem('selectedCountry', selectedCountry);
|
||||||
|
localStorage.setItem('selectedState', selectedState);
|
||||||
|
localStorage.setItem('selectedStateCode', selectedStateCode);
|
||||||
localStorage.setItem('daysOff', daysOff);
|
localStorage.setItem('daysOff', daysOff);
|
||||||
}
|
}
|
||||||
adjustInputWidth(event.target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateHolidays() {
|
function updateHolidays() {
|
||||||
const countryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry);
|
const countryCode = Object.keys(countriesList).find(code => countriesList[code] === selectedCountry);
|
||||||
if (countryCode) {
|
if (countryCode) {
|
||||||
holidays = getHolidaysForYear(countryCode, year);
|
holidays = getHolidaysForYear(countryCode, year, selectedStateCode);
|
||||||
optimizedDaysOff = optimizeDaysOff(holidays, year, daysOff);
|
optimizedDaysOff = optimizeDaysOff(holidays, year, daysOff);
|
||||||
consecutiveDaysOff = calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year);
|
consecutiveDaysOff = calculateConsecutiveDaysOff(holidays, optimizedDaysOff, year);
|
||||||
} else {
|
} else {
|
||||||
@@ -103,9 +129,13 @@
|
|||||||
function resetToDefault() {
|
function resetToDefault() {
|
||||||
year = defaultYear;
|
year = defaultYear;
|
||||||
selectedCountry = defaultCountry;
|
selectedCountry = defaultCountry;
|
||||||
|
selectedState = '';
|
||||||
|
selectedStateCode = '';
|
||||||
daysOff = defaultDaysOff;
|
daysOff = defaultDaysOff;
|
||||||
localStorage.setItem('year', year);
|
localStorage.setItem('year', year);
|
||||||
localStorage.setItem('selectedCountry', selectedCountry);
|
localStorage.setItem('selectedCountry', selectedCountry);
|
||||||
|
localStorage.setItem('selectedState', selectedState);
|
||||||
|
localStorage.setItem('selectedStateCode', selectedStateCode);
|
||||||
localStorage.setItem('daysOff', daysOff);
|
localStorage.setItem('daysOff', daysOff);
|
||||||
updateHolidays();
|
updateHolidays();
|
||||||
}
|
}
|
||||||
@@ -137,17 +167,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustInputWidth(inputElement) {
|
function adjustInputWidth(inputElement, value) {
|
||||||
const tempSpan = document.createElement('span');
|
if (typeof window !== 'undefined' && inputElement) { // Ensure this runs only in the browser
|
||||||
tempSpan.style.visibility = 'hidden';
|
const tempSpan = document.createElement('span');
|
||||||
tempSpan.style.position = 'absolute';
|
tempSpan.style.visibility = 'hidden';
|
||||||
tempSpan.style.whiteSpace = 'nowrap';
|
tempSpan.style.position = 'absolute';
|
||||||
tempSpan.textContent = inputElement.value || inputElement.placeholder;
|
tempSpan.style.whiteSpace = 'nowrap';
|
||||||
document.body.appendChild(tempSpan);
|
tempSpan.textContent = value || inputElement.value || inputElement.placeholder;
|
||||||
inputElement.style.width = `${tempSpan.offsetWidth + 30}px`;
|
document.body.appendChild(tempSpan);
|
||||||
document.body.removeChild(tempSpan);
|
inputElement.style.width = `${tempSpan.offsetWidth + 30}px`;
|
||||||
|
document.body.removeChild(tempSpan);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reactive statements to adjust input width when values change
|
||||||
|
$: if (countriesInput) adjustInputWidth(countriesInput, selectedCountry);
|
||||||
|
$: if (statesInput) adjustInputWidth(statesInput, selectedState);
|
||||||
|
|
||||||
function getFlagEmoji(countryCode) {
|
function getFlagEmoji(countryCode) {
|
||||||
if (!countryCode) return ''; // Return an empty string if countryCode is not available
|
if (!countryCode) return ''; // Return an empty string if countryCode is not available
|
||||||
return countryCode
|
return countryCode
|
||||||
@@ -411,7 +447,14 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>🌴 Stretch My Time Off</h2>
|
<h2>🌴 Stretch My Time Off</h2>
|
||||||
<p>
|
<p>
|
||||||
In <strong>{getFlagEmoji(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))} {selectedCountry}</strong>, there are <strong>{holidays.length}</strong> public holidays in <strong>{year}</strong>.
|
In
|
||||||
|
<strong>
|
||||||
|
{getFlagEmoji(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))}
|
||||||
|
{#if selectedState}
|
||||||
|
{selectedState},
|
||||||
|
{/if}
|
||||||
|
{selectedCountry}
|
||||||
|
</strong>, there are <strong>{holidays.length}</strong> public holidays in <strong>{year}</strong>.
|
||||||
<br />
|
<br />
|
||||||
Let's stretch your time off from <strong>{daysOff} days</strong> to <strong>{consecutiveDaysOff.reduce((total, group) => total + group.totalDays, 0)} days</strong> (<a href="#how-it-works" on:click={toggleHowItWorks}>how?</a>)
|
Let's stretch your time off from <strong>{daysOff} days</strong> to <strong>{consecutiveDaysOff.reduce((total, group) => total + group.totalDays, 0)} days</strong> (<a href="#how-it-works" on:click={toggleHowItWorks}>how?</a>)
|
||||||
</p>
|
</p>
|
||||||
@@ -421,7 +464,16 @@
|
|||||||
<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(Object.keys(countriesList).find(code => countriesList[code] === selectedCountry))}</span>
|
||||||
<input bind:this={inputElement} list="countries" class="editable-input bold" bind:value={selectedCountry} placeholder={placeholder} on:input={handleCountryChange} on:focus={() => { inputElement.value = ''; adjustInputWidth(); }} aria-label="Select country" />
|
{#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" />
|
||||||
|
<datalist id="states">
|
||||||
|
{#each Object.entries(statesList) as [code, name]}
|
||||||
|
<option value={name}>{name}</option>
|
||||||
|
{/each}
|
||||||
|
</datalist>
|
||||||
|
in
|
||||||
|
{/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" />
|
||||||
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user