Release Notes Architecture - Component refactor: removed monoliths (`Calendar.vue`, `CalendarGrid.vue`); expanded granular view + header/control components. - Dialog system introduced (`BaseDialog`, `SettingsDialog`). State & Data - Store redesigned: Map-based events + recurrence map; mutation counters. - Local persistence + undo/redo history (custom plugins). Date & Holidays - Migrated all date logic to `date-fns` (+ tz). - Added national holiday support (toggle + loading utilities). Recurrence & Events - Consolidated recurrence handling; fixes for monthly edge days (29–31), annual, multi‑day, and complex weekly repeats. - Reliable splitting/moving/resizing/deletion of repeating and multi‑day events. Interaction & UX - Double‑tap to create events; improved drag (multi‑day + position retention). - Scroll & inertial/momentum navigation; year change via numeric scroller. - Movable event dialog; live settings application. Performance - Progressive / virtual week rendering, reduced off‑screen buffer. - Targeted repaint strategy; minimized full re-renders. Plugins Added - History, undo normalization, persistence, scroll manager, virtual weeks. Styling & Layout - Responsive + compact layout refinements; header restructured. - Simplified visual elements (removed dots/overflow text); holiday styling adjustments. Reliability / Fixes - Numerous recurrence, deletion, orientation/rotation, and event indexing corrections. - Cross-browser fallback (Firefox week info). Dependencies Added - date-fns, date-fns-tz, date-holidays, pinia-plugin-persistedstate. Net Change - 28 files modified; ~4.4K insertions / ~2.2K deletions (major refactor + feature set).
180 lines
4.6 KiB
JavaScript
180 lines
4.6 KiB
JavaScript
// holidays.js — Holiday utilities using date-holidays package
|
|
import Holidays from 'date-holidays'
|
|
|
|
let holidaysInstance = null
|
|
let currentCountry = null
|
|
let currentState = null
|
|
let currentRegion = null
|
|
let holidayCache = new Map()
|
|
let yearCache = new Map()
|
|
|
|
/**
|
|
* Initialize holidays for a specific country/region
|
|
* @param {string} country - Country code (e.g., 'US', 'GB', 'DE')
|
|
* @param {string} [state] - State/province code (e.g., 'CA' for California)
|
|
* @param {string} [region] - Region code
|
|
*/
|
|
export function initializeHolidays(country, state = null, region = null) {
|
|
if (!country) {
|
|
console.warn('No country provided for holiday initialization')
|
|
holidaysInstance = null
|
|
return false
|
|
}
|
|
|
|
try {
|
|
holidaysInstance = new Holidays(country, state, region)
|
|
currentCountry = country
|
|
currentState = state
|
|
currentRegion = region
|
|
|
|
holidayCache.clear()
|
|
yearCache.clear()
|
|
|
|
return true
|
|
} catch (error) {
|
|
console.warn('Failed to initialize holidays for', country, state, region, error)
|
|
holidaysInstance = null
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get holidays for a specific year
|
|
* @param {number} year - The year to get holidays for
|
|
* @returns {Array} Array of holiday objects
|
|
*/
|
|
export function getHolidaysForYear(year) {
|
|
if (!holidaysInstance) {
|
|
return []
|
|
}
|
|
|
|
if (yearCache.has(year)) {
|
|
return yearCache.get(year)
|
|
}
|
|
|
|
try {
|
|
const holidays = holidaysInstance.getHolidays(year)
|
|
yearCache.set(year, holidays)
|
|
return holidays
|
|
} catch (error) {
|
|
console.warn('Failed to get holidays for year', year, error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get holiday for a specific date
|
|
* @param {string|Date} date - Date in YYYY-MM-DD format or Date object
|
|
* @returns {Object|null} Holiday object or null if no holiday
|
|
*/
|
|
export function getHolidayForDate(date) {
|
|
if (!holidaysInstance) {
|
|
return null
|
|
}
|
|
|
|
const cacheKey = typeof date === 'string' ? date : date.toISOString().split('T')[0]
|
|
if (holidayCache.has(cacheKey)) {
|
|
return holidayCache.get(cacheKey)
|
|
}
|
|
|
|
try {
|
|
let dateObj
|
|
if (typeof date === 'string') {
|
|
const [year, month, day] = date.split('-').map(Number)
|
|
dateObj = new Date(year, month - 1, day)
|
|
} else {
|
|
dateObj = date
|
|
}
|
|
|
|
const year = dateObj.getFullYear()
|
|
const holidays = getHolidaysForYear(year)
|
|
|
|
const holiday = holidays.find((h) => {
|
|
const holidayDate = new Date(h.date)
|
|
return (
|
|
holidayDate.getFullYear() === dateObj.getFullYear() &&
|
|
holidayDate.getMonth() === dateObj.getMonth() &&
|
|
holidayDate.getDate() === dateObj.getDate()
|
|
)
|
|
})
|
|
|
|
const result = holiday || null
|
|
holidayCache.set(cacheKey, result)
|
|
|
|
return result
|
|
} catch (error) {
|
|
console.warn('Failed to get holiday for date', date, error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a date is a holiday
|
|
* @param {string|Date} date - Date in YYYY-MM-DD format or Date object
|
|
* @returns {boolean} True if the date is a holiday
|
|
*/
|
|
export function isHoliday(date) {
|
|
return getHolidayForDate(date) !== null
|
|
}
|
|
|
|
/**
|
|
* Get available countries for holidays
|
|
* @returns {Array} Array of country codes
|
|
*/
|
|
export function getAvailableCountries() {
|
|
try {
|
|
const holidays = new Holidays()
|
|
const countries = holidays.getCountries()
|
|
|
|
// The getCountries method might return an object, convert to array of keys
|
|
if (countries && typeof countries === 'object') {
|
|
return Array.isArray(countries) ? countries : Object.keys(countries)
|
|
}
|
|
|
|
return ['US', 'GB', 'DE', 'FR', 'CA', 'AU'] // Fallback
|
|
} catch (error) {
|
|
console.warn('Failed to get available countries', error)
|
|
return ['US', 'GB', 'DE', 'FR', 'CA', 'AU'] // Fallback to common countries
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get available states/regions for a country
|
|
* @param {string} country - Country code
|
|
* @returns {Array} Array of state/region codes
|
|
*/
|
|
export function getAvailableStates(country) {
|
|
try {
|
|
if (!country) return []
|
|
|
|
const holidays = new Holidays()
|
|
const states = holidays.getStates(country)
|
|
|
|
// The getStates method might return an object, convert to array of keys
|
|
if (states && typeof states === 'object') {
|
|
return Array.isArray(states) ? states : Object.keys(states)
|
|
}
|
|
|
|
return []
|
|
} catch (error) {
|
|
console.warn('Failed to get available states for', country, error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get holiday configuration info
|
|
* @returns {Object} Current holiday configuration
|
|
*/
|
|
export function getHolidayConfig() {
|
|
return {
|
|
country: currentCountry,
|
|
state: currentState,
|
|
region: currentRegion,
|
|
initialized: !!holidaysInstance,
|
|
}
|
|
}
|
|
|
|
// Initialize with US holidays by default
|
|
initializeHolidays('US')
|