Cleanup
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
export let month;
|
||||
export let holidays = [];
|
||||
export let optimizedDaysOff = [];
|
||||
export let consecutiveDaysOff = [];
|
||||
|
||||
// Reactive declarations
|
||||
$: daysInMonth = getDaysInMonth(year, month);
|
||||
@@ -33,16 +34,55 @@
|
||||
date.getDate() === day
|
||||
);
|
||||
}
|
||||
|
||||
// Determine the dominant month for each consecutive days off period
|
||||
function getDominantMonth(period) {
|
||||
const startMonth = period.startDate.getMonth();
|
||||
const endMonth = period.endDate.getMonth();
|
||||
|
||||
if (startMonth === endMonth) {
|
||||
return startMonth;
|
||||
}
|
||||
|
||||
const startDays = getDaysInMonth(year, startMonth) - period.startDate.getDate() + 1;
|
||||
const endDays = period.endDate.getDate();
|
||||
|
||||
return startDays > endDays ? startMonth : endMonth;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="calendar">
|
||||
<div class="month-name">{new Date(year, month).toLocaleString('default', { month: 'long' })}</div>
|
||||
{#each Array.from({ length: firstDay }) as _}
|
||||
<div class="day"></div>
|
||||
{/each}
|
||||
{#each Array.from({ length: daysInMonth }, (_, i) => i + 1) as day}
|
||||
<div class="day {(firstDay + day - 1) % 7 === 0 || (firstDay + day - 1) % 7 === 6 ? 'weekend' : ''} {getHoliday(day) ? 'holiday' : ''} {isOptimizedDayOff(day) ? 'optimized' : ''}">
|
||||
{day}
|
||||
{#if getHoliday(day)}
|
||||
<Tooltip text={getHoliday(day).name} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="consecutive-days-off">
|
||||
<ul>
|
||||
{#each consecutiveDaysOff.filter(period => getDominantMonth(period) === month) as period}
|
||||
<li>
|
||||
{period.startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} to
|
||||
{period.endDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}:
|
||||
<strong>{period.totalDays} days</strong>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.calendar {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 1px;
|
||||
border: 1px solid #444;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
@@ -58,7 +98,6 @@
|
||||
background-color: #222;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.day:hover {
|
||||
:global(.tooltip) {
|
||||
opacity: 1;
|
||||
@@ -66,37 +105,34 @@
|
||||
}
|
||||
}
|
||||
.weekend {
|
||||
background-color: #333;
|
||||
background-color: #585858;
|
||||
}
|
||||
.optimized {
|
||||
background-color: #4caf50;
|
||||
}
|
||||
.holiday {
|
||||
background-color: #3b1e6e;
|
||||
cursor: pointer;
|
||||
}
|
||||
.optimized {
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
.month-name {
|
||||
grid-column: span 7;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
margin-bottom: 2px;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 0.9em;
|
||||
text-transform: uppercase;
|
||||
color: #c5c6c7;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.consecutive-days-off {
|
||||
margin-top: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="calendar">
|
||||
<div class="month-name">{new Date(year, month).toLocaleString('default', { month: 'long' })}</div>
|
||||
{#each Array.from({ length: firstDay }) as _}
|
||||
<div class="day"></div>
|
||||
{/each}
|
||||
{#each Array.from({ length: daysInMonth }, (_, i) => i + 1) as day}
|
||||
<div class="day {(firstDay + day - 1) % 7 === 0 || (firstDay + day - 1) % 7 === 6 ? 'weekend' : ''} {getHoliday(day) ? 'holiday' : ''} {isOptimizedDayOff(day) ? 'optimized' : ''}">
|
||||
{day}
|
||||
{#if getHoliday(day)}
|
||||
<Tooltip text={getHoliday(day).name} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
.consecutive-days-off ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.consecutive-days-off li {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user