refactor: Datums- und Zeitraster-Helfer nach src/lib/weekly/date-utils.ts
Zweiter Schnitt an WeeklyView.tsx (D1). 13 reine Funktionen ohne Komponenten-State: getStartOfWeek, getDayName, getTimeSlots, formatHour, getWeekNumber, formatCustomHeader und weitere. 11.830 -> 11.655 Zeilen. Mit npm run build verifiziert (Exit 0). Nebenbefund: getWeekNumber existiert im Projekt dreimal -- hier, in WeekPrintModal.tsx und in api/user/export-week-view/route.ts. Das Ersetzen der Duplikate steht in TODO.md; vorher gehoeren die drei Implementierungen auf Gleichheit geprueft. v1.117.6 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
97cb306853
commit
c303e1cf2b
13
CHANGELOG.md
13
CHANGELOG.md
@ -2,6 +2,19 @@
|
||||
|
||||
All notable changes to this project are documented in this file.
|
||||
|
||||
## [1.117.6] - 2026-09-07
|
||||
|
||||
### Changed
|
||||
- Datums-, Wochen- und Zeitraster-Helfer nach `src/lib/weekly/date-utils.ts` verschoben
|
||||
(13 reine Funktionen ohne Komponenten-State: `getStartOfWeek`, `getDayName`,
|
||||
`getTimeSlots`, `formatHour`, `getWeekNumber`, `formatCustomHeader` und weitere).
|
||||
`WeeklyView.tsx`: 11.830 → 11.655 Zeilen. Mit `npm run build` verifiziert (Exit 0).
|
||||
|
||||
### Notes
|
||||
- `getWeekNumber` existiert im Projekt **dreimal** — hier, in `WeekPrintModal.tsx` und in
|
||||
`app/api/user/export-week-view/route.ts`. Die Duplikate zu ersetzen steht in TODO.md;
|
||||
vorher müssen die drei Implementierungen auf Gleichheit geprüft werden.
|
||||
|
||||
## [1.117.5] - 2026-09-07
|
||||
|
||||
### Changed
|
||||
|
||||
9
TODO.md
9
TODO.md
@ -128,7 +128,7 @@ Betroffen sind genau zwei Dateien:
|
||||
|
||||
| Datei | Zeilen |
|
||||
|---|---:|
|
||||
| `src/components/WeeklyView.tsx` | 11.830 *(war 11.906)* |
|
||||
| `src/components/WeeklyView.tsx` | 11.655 *(war 11.906)* |
|
||||
| `src/components/SettingsSidebar.tsx` | 5.445 |
|
||||
|
||||
**Voraussetzung:** C1 muss laufen — ohne Typecheck ist ein Refactoring dieser Größe
|
||||
@ -137,7 +137,12 @@ Blindflug. Ebenso hilfreich: A2, damit es überhaupt Tests gibt.
|
||||
#### WeeklyView.tsx → Zielstruktur
|
||||
- [x] Typen nach `src/lib/weekly/types.ts` (7 Typen, 97 Zeilen) — Build verifiziert
|
||||
- [ ] `src/components/weekly/` anlegen
|
||||
- [ ] Reine Helfer nach `src/lib/weekly/` (Datum, Slots, Formatierung)
|
||||
- [x] Datums-/Zeitraster-Helfer nach `src/lib/weekly/date-utils.ts` (13 Funktionen, 202 Zeilen)
|
||||
- [ ] Cookie-/Geräteeinstellungen nach `src/lib/weekly/device-settings.ts`
|
||||
- [ ] Formatierungs-Helfer (`stripHtml`, `invertColor`, `isAllDayEvent`) auslagern
|
||||
- [ ] `getWeekNumber` existiert **dreimal** im Projekt (WeeklyView, `WeekPrintModal.tsx`,
|
||||
`api/user/export-week-view/route.ts`) — Duplikate gegen `date-utils.ts` ersetzen,
|
||||
vorher die drei Implementierungen auf Gleichheit prüfen
|
||||
- [ ] Drag & Drop in einen Hook `useTaskDragDrop.ts`
|
||||
- [ ] Someday-Listen in `SomedayLists.tsx`
|
||||
- [ ] All-Day-Leiste in `AllDayBar.tsx`
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.117.5",
|
||||
"version": "1.117.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.117.5",
|
||||
"version": "1.117.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.11.1",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.117.5",
|
||||
"version": "1.117.6",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -213,195 +213,23 @@ const useGoogleFonts = (fonts: string[]) => {
|
||||
}, [fonts]);
|
||||
};
|
||||
|
||||
// Date utilities
|
||||
function getStartOfWeek(date: Date, startDay: number = 0): Date {
|
||||
const d = new Date(date);
|
||||
const day = d.getDay();
|
||||
const diff = d.getDate() - day + (day < startDay ? -7 : 0) + startDay; // if today is sun(0) and start is mon(1), day < start (0 < 1) -> -7 + 1 = -6. 0 - 6 = -6. Correct.
|
||||
// Wait, let's re-verify:
|
||||
// Start Mon(1). Today Sun(0). day=0. diff = date - 0 + (-7) + 1 = date - 6. Correct (last Monday).
|
||||
// Start Mon(1). Today Mon(1). day=1. diff = date - 1 + (0) + 1 = date. Correct.
|
||||
// Start Sun(0). Today Mon(1). day=1. diff = date - 1 + (0) + 0 = date - 1. Correct (last Sunday).
|
||||
// Start Sun(0). Today Sun(0). day=0. diff = date - 0 + (0) + 0 = date. Correct.
|
||||
// What if Start Mon(1), Today Tue(2). day=2. diff = date - 2 + 0 + 1 = date - 1. Correct.
|
||||
import {
|
||||
getStartOfWeek, formatDateHeader, getDayName, isSameDay, formatDateToISO,
|
||||
parseCalendarDate, formatHour, getTimeSlots, getHourFromSlot, getWeekNumber,
|
||||
getCWReferenceDate, formatCustomHeader, getSelectedDay,
|
||||
} from "@/lib/weekly/date-utils";
|
||||
|
||||
// Better logic:
|
||||
// const day = d.getDay();
|
||||
// const diff = (day < startDay ? 7 : 0) + day - startDay;
|
||||
// d.setDate(d.getDate() - diff);
|
||||
//
|
||||
// Let's stick to a robust one:
|
||||
const currentDay = d.getDay();
|
||||
const distance = (currentDay - startDay + 7) % 7;
|
||||
d.setDate(d.getDate() - distance);
|
||||
return d;
|
||||
}
|
||||
|
||||
function formatDateHeader(date: Date, locale: string = "en-US"): string {
|
||||
return date.toLocaleDateString(locale, { day: "numeric", month: "short" }); // e.g. 12. Feb.
|
||||
}
|
||||
|
||||
function getDayName(date: Date, locale: string = "en-US", format?: string, customNames?: string, weekStartDay: number = 0, dayCase: string = "capitalize"): string {
|
||||
let name: string;
|
||||
if (format === "custom" && customNames) {
|
||||
// Split by comma or semicolon to allow spaces in names
|
||||
const names = customNames.split(/[,;]+/).map(s => s.trim()).filter(Boolean);
|
||||
if (names.length === 7) {
|
||||
// Adjust index based on weekStartDay (0=Sun, 1=Mon)
|
||||
const index = (date.getDay() - weekStartDay + 7) % 7;
|
||||
name = names[index];
|
||||
} else {
|
||||
name = date.toLocaleDateString(locale, { weekday: "long" });
|
||||
}
|
||||
} else {
|
||||
const weekdayOption = format === "narrow" ? "narrow" : (format === "short" ? "short" : "long");
|
||||
try {
|
||||
name = date.toLocaleDateString(locale, { weekday: weekdayOption });
|
||||
} catch (e) {
|
||||
name = date.toLocaleDateString("en-US", { weekday: weekdayOption });
|
||||
}
|
||||
}
|
||||
if (dayCase === "uppercase") return name.toUpperCase();
|
||||
if (dayCase === "normal") return name.toLowerCase();
|
||||
// capitalize: first letter uppercase, rest lowercase
|
||||
return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
function isSameDay(d1: Date, d2: Date): boolean {
|
||||
return d1.toDateString() === d2.toDateString();
|
||||
}
|
||||
|
||||
function formatDateToISO(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a date string from a calendar event.
|
||||
* If strictly a date (YYYY-MM-DD), it's parsed as local mid-night.
|
||||
* If an ISO string with time, it's parsed regularly.
|
||||
*/
|
||||
function parseCalendarDate(dateStr: string): Date {
|
||||
if (!dateStr) return new Date();
|
||||
// If it's date-only (YYYY-MM-DD), parse as local midnight
|
||||
if (!dateStr.includes("T")) {
|
||||
const parts = dateStr.split("-").map(Number);
|
||||
if (parts.length === 3) {
|
||||
return new Date(parts[0], parts[1] - 1, parts[2], 0, 0, 0);
|
||||
}
|
||||
}
|
||||
// If it's an ISO string but we want local midnight (e.g. from cache or older backend)
|
||||
// we still parse it. The fix in the backend should reduce this.
|
||||
return new Date(dateStr);
|
||||
}
|
||||
|
||||
function formatHour(hour: number, minutes: number = 0, format: "short" | "full" = "short", timeFormat: string = "24h"): string {
|
||||
if (timeFormat === "12h") {
|
||||
const h = hour % 12 || 12;
|
||||
const ampm = hour >= 12 ? "PM" : "AM";
|
||||
const m = minutes > 0 ? `:${minutes.toString().padStart(2, "0")}` : "";
|
||||
return format === "full" || minutes > 0 ? `${h}:${minutes.toString().padStart(2, "0")} ${ampm}` : `${h}${m} ${ampm}`;
|
||||
}
|
||||
// 24h format
|
||||
if (format === "short" && minutes === 0) {
|
||||
return `${hour}`;
|
||||
}
|
||||
return `${hour.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function getTimeSlots(
|
||||
cellDuration: CellDuration,
|
||||
startHour: number,
|
||||
endHour: number,
|
||||
): string[] {
|
||||
const slots: string[] = [];
|
||||
const startMins = startHour * 60;
|
||||
const endMins = endHour * 60;
|
||||
for (let mins = startMins; mins < endMins; mins += cellDuration) {
|
||||
const h = Math.floor(mins / 60);
|
||||
const m = mins % 60;
|
||||
slots.push(`${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`);
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
function getHourFromSlot(slot: string): number {
|
||||
return parseInt(slot.split(":")[0], 10);
|
||||
}
|
||||
|
||||
function getWeekNumber(date: Date): number {
|
||||
// ISO 8601 week number: weeks start on Monday
|
||||
const d = new Date(
|
||||
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
|
||||
);
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
||||
}
|
||||
|
||||
// Get the Monday of the ISO week that the first visible day belongs to.
|
||||
// This ensures CW changes when the first visible day crosses into a new ISO week.
|
||||
function getCWReferenceDate(days: Date[]): Date {
|
||||
if (days.length === 0) return new Date();
|
||||
const first = days[0];
|
||||
const dow = first.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
||||
// Calculate distance back to Monday (ISO week start)
|
||||
// Sunday (0) → go back 6 days to previous Monday
|
||||
// Monday (1) → 0, Tuesday (2) → 1, etc.
|
||||
const distToMonday = dow === 0 ? 6 : dow - 1;
|
||||
return new Date(first.getTime() - distToMonday * 86400000);
|
||||
}
|
||||
|
||||
// Format a custom header string using tokens
|
||||
function formatCustomHeader(format: string, days: Date[], language: string, t: any, refDateOverride?: Date): string {
|
||||
if (!format) return "";
|
||||
|
||||
// Choose the reference date: if today is within the visible days, use today.
|
||||
// Otherwise, use the standard CW reference date (start of week).
|
||||
const today = new Date();
|
||||
const isTodayInWeek = days.some(d =>
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
d.getFullYear() === today.getFullYear()
|
||||
);
|
||||
const refDate = refDateOverride ?? (isTodayInWeek ? today : getCWReferenceDate(days));
|
||||
|
||||
// Define token mappings
|
||||
const tokens: Record<string, string> = {
|
||||
"YYYY": refDate.getFullYear().toString(),
|
||||
"WW": getWeekNumber(refDate).toString().padStart(2, '0'),
|
||||
"MMMM": refDate.toLocaleDateString(language, { month: 'long' }),
|
||||
"MMM": refDate.toLocaleDateString(language, { month: 'short' }),
|
||||
"MM": (refDate.getMonth() + 1).toString().padStart(2, '0'),
|
||||
"M": (refDate.getMonth() + 1).toString(),
|
||||
"DDDD": refDate.toLocaleDateString(language, { weekday: 'long' }),
|
||||
"DDD": refDate.toLocaleDateString(language, { weekday: 'short' }),
|
||||
"DD": refDate.getDate().toString().padStart(2, '0'),
|
||||
"D": refDate.getDate().toString(),
|
||||
"[TODAY]": today.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
};
|
||||
|
||||
// Single-pass replacement using regex to avoid nested replacements (e.g. M in MMMM)
|
||||
// Standalone 'W' removed to allow literal 'W' (like in 'KW')
|
||||
const regex = /\[TODAY\]|YYYY|WW|MMMM|MMM|MM|M|DDDD|DDD|DD|D/g;
|
||||
|
||||
return format.replace(regex, (match) => tokens[match] || match);
|
||||
}
|
||||
|
||||
// Get the "selected" day for current_day header.
|
||||
// Priority: explicit selection → today if visible → first visible day
|
||||
function getSelectedDay(days: Date[], explicitSelection?: Date | null): Date {
|
||||
if (explicitSelection) return explicitSelection;
|
||||
const today = new Date();
|
||||
return days.some(d =>
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
d.getFullYear() === today.getFullYear()
|
||||
) ? today : days[0];
|
||||
}
|
||||
|
||||
// Check if an event is an all-day event
|
||||
// Defined outside component to avoid stale closure issues in useCallbacks
|
||||
|
||||
201
src/lib/weekly/date-utils.ts
Normal file
201
src/lib/weekly/date-utils.ts
Normal file
@ -0,0 +1,201 @@
|
||||
import type { CellDuration } from "@/lib/weekly/types";
|
||||
|
||||
/**
|
||||
* Datums-, Wochen- und Zeitraster-Helfer der Wochenansicht.
|
||||
*
|
||||
* Reine Funktionen ohne Komponenten-State — lagen bis v1.117.6 in WeeklyView.tsx.
|
||||
*
|
||||
* Hinweis: getWeekNumber existiert im Projekt noch zweimal separat, in
|
||||
* WeekPrintModal.tsx und in app/api/user/export-week-view/route.ts. Diese Duplikate
|
||||
* gegen diese Fassung zu ersetzen ist ein eigener Schritt (siehe TODO.md D1).
|
||||
*/
|
||||
|
||||
// Date utilities
|
||||
export function getStartOfWeek(date: Date, startDay: number = 0): Date {
|
||||
const d = new Date(date);
|
||||
const day = d.getDay();
|
||||
const diff = d.getDate() - day + (day < startDay ? -7 : 0) + startDay; // if today is sun(0) and start is mon(1), day < start (0 < 1) -> -7 + 1 = -6. 0 - 6 = -6. Correct.
|
||||
// Wait, let's re-verify:
|
||||
// Start Mon(1). Today Sun(0). day=0. diff = date - 0 + (-7) + 1 = date - 6. Correct (last Monday).
|
||||
// Start Mon(1). Today Mon(1). day=1. diff = date - 1 + (0) + 1 = date. Correct.
|
||||
// Start Sun(0). Today Mon(1). day=1. diff = date - 1 + (0) + 0 = date - 1. Correct (last Sunday).
|
||||
// Start Sun(0). Today Sun(0). day=0. diff = date - 0 + (0) + 0 = date. Correct.
|
||||
// What if Start Mon(1), Today Tue(2). day=2. diff = date - 2 + 0 + 1 = date - 1. Correct.
|
||||
|
||||
// Better logic:
|
||||
// const day = d.getDay();
|
||||
// const diff = (day < startDay ? 7 : 0) + day - startDay;
|
||||
// d.setDate(d.getDate() - diff);
|
||||
//
|
||||
// Let's stick to a robust one:
|
||||
const currentDay = d.getDay();
|
||||
const distance = (currentDay - startDay + 7) % 7;
|
||||
d.setDate(d.getDate() - distance);
|
||||
return d;
|
||||
}
|
||||
|
||||
export function formatDateHeader(date: Date, locale: string = "en-US"): string {
|
||||
return date.toLocaleDateString(locale, { day: "numeric", month: "short" }); // e.g. 12. Feb.
|
||||
}
|
||||
|
||||
export function getDayName(date: Date, locale: string = "en-US", format?: string, customNames?: string, weekStartDay: number = 0, dayCase: string = "capitalize"): string {
|
||||
let name: string;
|
||||
if (format === "custom" && customNames) {
|
||||
// Split by comma or semicolon to allow spaces in names
|
||||
const names = customNames.split(/[,;]+/).map(s => s.trim()).filter(Boolean);
|
||||
if (names.length === 7) {
|
||||
// Adjust index based on weekStartDay (0=Sun, 1=Mon)
|
||||
const index = (date.getDay() - weekStartDay + 7) % 7;
|
||||
name = names[index];
|
||||
} else {
|
||||
name = date.toLocaleDateString(locale, { weekday: "long" });
|
||||
}
|
||||
} else {
|
||||
const weekdayOption = format === "narrow" ? "narrow" : (format === "short" ? "short" : "long");
|
||||
try {
|
||||
name = date.toLocaleDateString(locale, { weekday: weekdayOption });
|
||||
} catch (e) {
|
||||
name = date.toLocaleDateString("en-US", { weekday: weekdayOption });
|
||||
}
|
||||
}
|
||||
if (dayCase === "uppercase") return name.toUpperCase();
|
||||
if (dayCase === "normal") return name.toLowerCase();
|
||||
// capitalize: first letter uppercase, rest lowercase
|
||||
return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
|
||||
}
|
||||
|
||||
export function isSameDay(d1: Date, d2: Date): boolean {
|
||||
return d1.toDateString() === d2.toDateString();
|
||||
}
|
||||
|
||||
export function formatDateToISO(date: Date): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a date string from a calendar event.
|
||||
* If strictly a date (YYYY-MM-DD), it's parsed as local mid-night.
|
||||
* If an ISO string with time, it's parsed regularly.
|
||||
*/
|
||||
export function parseCalendarDate(dateStr: string): Date {
|
||||
if (!dateStr) return new Date();
|
||||
// If it's date-only (YYYY-MM-DD), parse as local midnight
|
||||
if (!dateStr.includes("T")) {
|
||||
const parts = dateStr.split("-").map(Number);
|
||||
if (parts.length === 3) {
|
||||
return new Date(parts[0], parts[1] - 1, parts[2], 0, 0, 0);
|
||||
}
|
||||
}
|
||||
// If it's an ISO string but we want local midnight (e.g. from cache or older backend)
|
||||
// we still parse it. The fix in the backend should reduce this.
|
||||
return new Date(dateStr);
|
||||
}
|
||||
|
||||
export function formatHour(hour: number, minutes: number = 0, format: "short" | "full" = "short", timeFormat: string = "24h"): string {
|
||||
if (timeFormat === "12h") {
|
||||
const h = hour % 12 || 12;
|
||||
const ampm = hour >= 12 ? "PM" : "AM";
|
||||
const m = minutes > 0 ? `:${minutes.toString().padStart(2, "0")}` : "";
|
||||
return format === "full" || minutes > 0 ? `${h}:${minutes.toString().padStart(2, "0")} ${ampm}` : `${h}${m} ${ampm}`;
|
||||
}
|
||||
// 24h format
|
||||
if (format === "short" && minutes === 0) {
|
||||
return `${hour}`;
|
||||
}
|
||||
return `${hour.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function getTimeSlots(
|
||||
cellDuration: CellDuration,
|
||||
startHour: number,
|
||||
endHour: number,
|
||||
): string[] {
|
||||
const slots: string[] = [];
|
||||
const startMins = startHour * 60;
|
||||
const endMins = endHour * 60;
|
||||
for (let mins = startMins; mins < endMins; mins += cellDuration) {
|
||||
const h = Math.floor(mins / 60);
|
||||
const m = mins % 60;
|
||||
slots.push(`${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}`);
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
export function getHourFromSlot(slot: string): number {
|
||||
return parseInt(slot.split(":")[0], 10);
|
||||
}
|
||||
|
||||
export function getWeekNumber(date: Date): number {
|
||||
// ISO 8601 week number: weeks start on Monday
|
||||
const d = new Date(
|
||||
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
|
||||
);
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
||||
}
|
||||
|
||||
// Get the Monday of the ISO week that the first visible day belongs to.
|
||||
// This ensures CW changes when the first visible day crosses into a new ISO week.
|
||||
export function getCWReferenceDate(days: Date[]): Date {
|
||||
if (days.length === 0) return new Date();
|
||||
const first = days[0];
|
||||
const dow = first.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
||||
// Calculate distance back to Monday (ISO week start)
|
||||
// Sunday (0) → go back 6 days to previous Monday
|
||||
// Monday (1) → 0, Tuesday (2) → 1, etc.
|
||||
const distToMonday = dow === 0 ? 6 : dow - 1;
|
||||
return new Date(first.getTime() - distToMonday * 86400000);
|
||||
}
|
||||
|
||||
// Format a custom header string using tokens
|
||||
export function formatCustomHeader(format: string, days: Date[], language: string, t: any, refDateOverride?: Date): string {
|
||||
if (!format) return "";
|
||||
|
||||
// Choose the reference date: if today is within the visible days, use today.
|
||||
// Otherwise, use the standard CW reference date (start of week).
|
||||
const today = new Date();
|
||||
const isTodayInWeek = days.some(d =>
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
d.getFullYear() === today.getFullYear()
|
||||
);
|
||||
const refDate = refDateOverride ?? (isTodayInWeek ? today : getCWReferenceDate(days));
|
||||
|
||||
// Define token mappings
|
||||
const tokens: Record<string, string> = {
|
||||
"YYYY": refDate.getFullYear().toString(),
|
||||
"WW": getWeekNumber(refDate).toString().padStart(2, '0'),
|
||||
"MMMM": refDate.toLocaleDateString(language, { month: 'long' }),
|
||||
"MMM": refDate.toLocaleDateString(language, { month: 'short' }),
|
||||
"MM": (refDate.getMonth() + 1).toString().padStart(2, '0'),
|
||||
"M": (refDate.getMonth() + 1).toString(),
|
||||
"DDDD": refDate.toLocaleDateString(language, { weekday: 'long' }),
|
||||
"DDD": refDate.toLocaleDateString(language, { weekday: 'short' }),
|
||||
"DD": refDate.getDate().toString().padStart(2, '0'),
|
||||
"D": refDate.getDate().toString(),
|
||||
"[TODAY]": today.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
};
|
||||
|
||||
// Single-pass replacement using regex to avoid nested replacements (e.g. M in MMMM)
|
||||
// Standalone 'W' removed to allow literal 'W' (like in 'KW')
|
||||
const regex = /\[TODAY\]|YYYY|WW|MMMM|MMM|MM|M|DDDD|DDD|DD|D/g;
|
||||
|
||||
return format.replace(regex, (match) => tokens[match] || match);
|
||||
}
|
||||
|
||||
// Get the "selected" day for current_day header.
|
||||
// Priority: explicit selection → today if visible → first visible day
|
||||
export function getSelectedDay(days: Date[], explicitSelection?: Date | null): Date {
|
||||
if (explicitSelection) return explicitSelection;
|
||||
const today = new Date();
|
||||
return days.some(d =>
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
d.getFullYear() === today.getFullYear()
|
||||
) ? today : days[0];
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user