Add French holidays to the timesheet, including Easter Monday
Implements logic to calculate and display French national holidays, such as Easter Monday, within the timesheet view. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 55837015-10e9-4be9-b857-7f5e6be73772 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 981f81d9-47f7-41fe-b3b4-19b5c1d2aa5d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1cc377db-7ea0-49f2-97ce-c3e87e0228cc/55837015-10e9-4be9-b857-7f5e6be73772/KSYTI3T Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
3f8e83b6ad
commit
a36c4a444a
@ -38,6 +38,51 @@ import { useAdminUnlocked } from "@/lib/admin-mode";
|
|||||||
import { getDaysInMonth, isWeekend, format } from "date-fns";
|
import { getDaysInMonth, isWeekend, format } from "date-fns";
|
||||||
import { fr } from "date-fns/locale";
|
import { fr } from "date-fns/locale";
|
||||||
|
|
||||||
|
function addDays(date: Date, days: number) {
|
||||||
|
const next = new Date(date);
|
||||||
|
next.setDate(next.getDate() + days);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEasterSunday(year: number) {
|
||||||
|
const a = year % 19;
|
||||||
|
const b = Math.floor(year / 100);
|
||||||
|
const c = year % 100;
|
||||||
|
const d = Math.floor(b / 4);
|
||||||
|
const e = b % 4;
|
||||||
|
const f = Math.floor((b + 8) / 25);
|
||||||
|
const g = Math.floor((b - f + 1) / 3);
|
||||||
|
const h = (19 * a + b - d - g + 15) % 30;
|
||||||
|
const i = Math.floor(c / 4);
|
||||||
|
const k = c % 4;
|
||||||
|
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
||||||
|
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
||||||
|
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
||||||
|
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
||||||
|
return new Date(year, month - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFrenchHolidayName(date: Date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const dateStr = format(date, "yyyy-MM-dd");
|
||||||
|
const easterSunday = getEasterSunday(year);
|
||||||
|
const holidays: Record<string, string> = {
|
||||||
|
[`${year}-01-01`]: "Jour de l'an",
|
||||||
|
[format(addDays(easterSunday, 1), "yyyy-MM-dd")]: "Lundi de Pâques",
|
||||||
|
[`${year}-05-01`]: "Fête du Travail",
|
||||||
|
[`${year}-05-08`]: "Victoire 1945",
|
||||||
|
[format(addDays(easterSunday, 39), "yyyy-MM-dd")]: "Ascension",
|
||||||
|
[format(addDays(easterSunday, 50), "yyyy-MM-dd")]: "Lundi de Pentecôte",
|
||||||
|
[`${year}-07-14`]: "Fête nationale",
|
||||||
|
[`${year}-08-15`]: "Assomption",
|
||||||
|
[`${year}-11-01`]: "Toussaint",
|
||||||
|
[`${year}-11-11`]: "Armistice 1918",
|
||||||
|
[`${year}-12-25`]: "Noël",
|
||||||
|
};
|
||||||
|
|
||||||
|
return holidays[dateStr] || null;
|
||||||
|
}
|
||||||
|
|
||||||
type LocalEntry = {
|
type LocalEntry = {
|
||||||
timesheetLineId: number;
|
timesheetLineId: number;
|
||||||
date: string;
|
date: string;
|
||||||
@ -100,10 +145,13 @@ export default function TimesheetDetailPage() {
|
|||||||
const daysCount = getDaysInMonth(new Date(timesheet.year, timesheet.month - 1));
|
const daysCount = getDaysInMonth(new Date(timesheet.year, timesheet.month - 1));
|
||||||
const arr = Array.from({ length: daysCount }, (_, i) => {
|
const arr = Array.from({ length: daysCount }, (_, i) => {
|
||||||
const date = new Date(timesheet.year, timesheet.month - 1, i + 1);
|
const date = new Date(timesheet.year, timesheet.month - 1, i + 1);
|
||||||
|
const holidayName = getFrenchHolidayName(date);
|
||||||
return {
|
return {
|
||||||
dateStr: format(date, "yyyy-MM-dd"),
|
dateStr: format(date, "yyyy-MM-dd"),
|
||||||
dayNum: i + 1,
|
dayNum: i + 1,
|
||||||
isWeekendDay: isWeekend(date),
|
isWeekendDay: isWeekend(date),
|
||||||
|
isHoliday: Boolean(holidayName),
|
||||||
|
holidayName,
|
||||||
dayName: format(date, "EE", { locale: fr })
|
dayName: format(date, "EE", { locale: fr })
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -404,8 +452,9 @@ export default function TimesheetDetailPage() {
|
|||||||
key={day.dateStr}
|
key={day.dateStr}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-0 py-1 font-medium text-center border-b border-r",
|
"px-0 py-1 font-medium text-center border-b border-r",
|
||||||
day.isWeekendDay ? "bg-muted/50" : ""
|
day.isWeekendDay || day.isHoliday ? "bg-muted/50" : ""
|
||||||
)}
|
)}
|
||||||
|
title={day.holidayName || undefined}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center leading-tight">
|
<div className="flex flex-col items-center leading-tight">
|
||||||
<span className="text-[9px] uppercase opacity-60">{day.dayName.slice(0, 2)}</span>
|
<span className="text-[9px] uppercase opacity-60">{day.dayName.slice(0, 2)}</span>
|
||||||
@ -466,11 +515,11 @@ export default function TimesheetDetailPage() {
|
|||||||
key={day.dateStr}
|
key={day.dateStr}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-0 text-center border-r transition-colors select-none",
|
"p-0 text-center border-r transition-colors select-none",
|
||||||
day.isWeekendDay ? "bg-muted/30" : "",
|
day.isWeekendDay || day.isHoliday ? "bg-muted/30" : "",
|
||||||
isEditable ? "cursor-pointer hover:bg-primary/10" : "",
|
isEditable ? "cursor-pointer hover:bg-primary/10" : "",
|
||||||
val > 0 ? "bg-primary/5 text-primary font-semibold" : ""
|
val > 0 ? "bg-primary/5 text-primary font-semibold" : ""
|
||||||
)}
|
)}
|
||||||
title={desc || undefined}
|
title={desc || day.holidayName || undefined}
|
||||||
>
|
>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Popover>
|
<Popover>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user