This commit is contained in:
Zachary
2024-11-11 18:19:45 +01:00
parent 4216b83837
commit 1c52afa674
5 changed files with 306 additions and 233 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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
});
}
}