Enhance calendar functionality with start date management and excluded month visibility. Added start date state and date picker for user-defined start dates. Updated holiday and PTO calculations to respect the start date. Improved UI to toggle excluded months and display active months based on the start date. Refactored related utility functions for better date handling.
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
export let consecutiveDaysOff: Array<{ startDate: Date; endDate: Date; totalDays: number }>;
|
||||
export let selectedCountryCode: string;
|
||||
export let weekendDays: number[] = [6, 0];
|
||||
export let startDate: Date = new Date(year, 0, 1);
|
||||
export let isActive: boolean = true;
|
||||
|
||||
// Function to determine the first day of the week based on locale
|
||||
function getFirstDayOfWeek(locale: string): number {
|
||||
@@ -84,12 +86,19 @@
|
||||
return weekendDays.includes(date.getDay());
|
||||
}
|
||||
|
||||
function isPastDate(day: number): boolean {
|
||||
const date = new Date(year, month, day);
|
||||
// Normalize startDate to current year for comparison
|
||||
const startDateInYear = new Date(year, startDate.getMonth(), startDate.getDate());
|
||||
return date < startDateInYear;
|
||||
}
|
||||
|
||||
const dayInitials = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
|
||||
|
||||
$: orderedDayInitials = dayInitials.slice(firstDayOfWeek).concat(dayInitials.slice(0, firstDayOfWeek));
|
||||
</script>
|
||||
|
||||
<div class="calendar">
|
||||
<div class="calendar {isActive ? '' : 'excluded-month'}">
|
||||
<div class="month-name">{new Date(year, month).toLocaleString('default', { month: 'long' })}</div>
|
||||
|
||||
{#each orderedDayInitials as dayInitial}
|
||||
@@ -101,7 +110,8 @@
|
||||
{/each}
|
||||
{#each Array.from({ length: daysInMonth }, (_, i) => i + 1) as day}
|
||||
{@const holiday = getHoliday(day)}
|
||||
<div class="day {isWeekend(new Date(year, month, day)) ? 'weekend' : ''} {holiday ? 'holiday' : ''} {isOptimizedDayOff(day) ? 'optimized' : ''} {isConsecutiveDayOff(day) ? 'consecutive-day' : ''}">
|
||||
{@const pastDate = isPastDate(day)}
|
||||
<div class="day {isWeekend(new Date(year, month, day)) ? 'weekend' : ''} {holiday ? 'holiday' : ''} {isOptimizedDayOff(day) ? 'optimized' : ''} {isConsecutiveDayOff(day) ? 'consecutive-day' : ''} {pastDate ? 'past-date' : ''}">
|
||||
<span class={holiday?.hidden ? 'strikethrough' : ''}>{day}</span>
|
||||
{#if holiday}
|
||||
<Tooltip text={holiday.name} />
|
||||
@@ -137,6 +147,14 @@
|
||||
color: #c5c6c7;
|
||||
font-size: 0.6em;
|
||||
}
|
||||
|
||||
.excluded-month .month-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.excluded-month .day-initial {
|
||||
color: #666;
|
||||
}
|
||||
.day {
|
||||
aspect-ratio: 1;
|
||||
text-align: center;
|
||||
@@ -202,4 +220,12 @@
|
||||
text-decoration: line-through;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.past-date {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.past-date span {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
@@ -31,49 +31,56 @@ export function getHolidaysForYear(countryCode: string, year: number, stateCode?
|
||||
}
|
||||
|
||||
// Find optimal placement of PTO days to maximize consecutive time off
|
||||
export function optimizeDaysOff(holidays: { date: Date }[], year: number, daysOff: number, weekendDays: number[] = [0, 6]): Date[] {
|
||||
export function optimizeDaysOff(holidays: { date: Date }[], year: number, daysOff: number, weekendDays: number[] = [0, 6], startDate?: Date): Date[] {
|
||||
const effectiveStartDate = startDate || new Date(year, 0, 1);
|
||||
const filteredHolidays = holidays.filter(h => h.date.getFullYear() === year && h.date >= effectiveStartDate);
|
||||
const allDaysOff = new Set([
|
||||
...holidays.filter(h => h.date.getFullYear() === year).map(h => dateKey(h.date)),
|
||||
...getWeekends(year, weekendDays).map(d => dateKey(d))
|
||||
...filteredHolidays.map(h => dateKey(h.date)),
|
||||
...getWeekends(year, weekendDays, effectiveStartDate).map(d => dateKey(d))
|
||||
]);
|
||||
|
||||
const gaps = findGaps(allDaysOff, year, weekendDays);
|
||||
const gaps = findGaps(allDaysOff, year, weekendDays, effectiveStartDate);
|
||||
return selectDaysOff(rankGapsByEfficiency(gaps, allDaysOff, weekendDays), daysOff, allDaysOff, weekendDays);
|
||||
}
|
||||
|
||||
// Calculate periods of consecutive days off (weekends + holidays + PTO)
|
||||
export function calculateConsecutiveDaysOff(holidays: { date: Date }[], optimizedDaysOff: Date[], year: number, weekendDays: number[] = [0, 6]) {
|
||||
export function calculateConsecutiveDaysOff(holidays: { date: Date }[], optimizedDaysOff: Date[], year: number, weekendDays: number[] = [0, 6], startDate?: Date) {
|
||||
const effectiveStartDate = startDate || new Date(year, 0, 1);
|
||||
const filteredHolidays = holidays.filter(h => h.date >= effectiveStartDate);
|
||||
const filteredOptimizedDaysOff = optimizedDaysOff.filter(d => d >= effectiveStartDate);
|
||||
|
||||
const allDaysOff = new Set([
|
||||
...holidays.map(h => dateKey(h.date)),
|
||||
...optimizedDaysOff.map(d => dateKey(d)),
|
||||
...getWeekends(year, weekendDays).map(d => dateKey(d))
|
||||
...filteredHolidays.map(h => dateKey(h.date)),
|
||||
...filteredOptimizedDaysOff.map(d => dateKey(d)),
|
||||
...getWeekends(year, weekendDays, effectiveStartDate).map(d => dateKey(d))
|
||||
]);
|
||||
|
||||
const consecutiveDaysOff = [];
|
||||
let currentGroup = [];
|
||||
|
||||
for (let d = new Date(year, 0, 1); d <= new Date(year, 11, 31); d.setDate(d.getDate() + 1)) {
|
||||
if (isWeekend(d, weekendDays) || isHoliday(d, holidays) || allDaysOff.has(dateKey(d))) {
|
||||
for (let d = new Date(effectiveStartDate); d <= new Date(year, 11, 31); d.setDate(d.getDate() + 1)) {
|
||||
if (isWeekend(d, weekendDays) || isHoliday(d, filteredHolidays) || allDaysOff.has(dateKey(d))) {
|
||||
currentGroup.push(new Date(d));
|
||||
} else if (currentGroup.length > 0) {
|
||||
if (isValidConsecutiveGroup(currentGroup, weekendDays)) {
|
||||
consecutiveDaysOff.push(createPeriod(currentGroup, optimizedDaysOff));
|
||||
consecutiveDaysOff.push(createPeriod(currentGroup, filteredOptimizedDaysOff));
|
||||
}
|
||||
currentGroup = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (currentGroup.length > 0 && isValidConsecutiveGroup(currentGroup, weekendDays)) {
|
||||
consecutiveDaysOff.push(createPeriod(currentGroup, optimizedDaysOff));
|
||||
consecutiveDaysOff.push(createPeriod(currentGroup, filteredOptimizedDaysOff));
|
||||
}
|
||||
|
||||
return consecutiveDaysOff;
|
||||
}
|
||||
|
||||
// Get all weekend days for a year
|
||||
function getWeekends(year: number, weekendDays: number[]): Date[] {
|
||||
function getWeekends(year: number, weekendDays: number[], startDate?: Date): Date[] {
|
||||
const effectiveStartDate = startDate || new Date(year, 0, 1);
|
||||
const weekends = [];
|
||||
for (let d = new Date(year, 0, 1); d <= new Date(year, 11, 31); d.setDate(d.getDate() + 1)) {
|
||||
for (let d = new Date(effectiveStartDate); d <= new Date(year, 11, 31); d.setDate(d.getDate() + 1)) {
|
||||
if (d.getMonth() === d.getMonth() && isWeekend(d, weekendDays)) {
|
||||
weekends.push(new Date(d));
|
||||
}
|
||||
@@ -82,11 +89,12 @@ function getWeekends(year: number, weekendDays: number[]): Date[] {
|
||||
}
|
||||
|
||||
// Find gaps between days off that could be filled with PTO
|
||||
function findGaps(allDaysOff: Set<string>, year: number, weekendDays: number[]) {
|
||||
function findGaps(allDaysOff: Set<string>, year: number, weekendDays: number[], startDate?: Date) {
|
||||
const effectiveStartDate = startDate || new Date(year, 0, 1);
|
||||
const gaps = [];
|
||||
let gapStart = null;
|
||||
|
||||
for (let d = new Date(year, 0, 1); d <= new Date(year, 11, 31); d.setDate(d.getDate() + 1)) {
|
||||
for (let d = new Date(effectiveStartDate); d <= new Date(year, 11, 31); d.setDate(d.getDate() + 1)) {
|
||||
if (!allDaysOff.has(dateKey(d)) && !isWeekend(d, weekendDays)) {
|
||||
if (!gapStart) gapStart = new Date(d);
|
||||
} else if (gapStart) {
|
||||
|
||||
Reference in New Issue
Block a user