diff --git a/package.json b/package.json index 4f3906b..394bada 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.32.0", + "version": "1.33.0", "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": { diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 3f5307e..28c23a9 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -263,6 +263,8 @@ const translations: Record = { headerDisplayMonth: "Month Name", headerDisplayMonthYear: "Month & Year", headerDisplayDate: "Full Date", + headerDisplayCustom: "Custom", + headerCustomFormatLabel: "Format string (e.g. DD.MM.YYYY)", language: "Language", dateFormat: "Date Format", timeFormat: "Time Format", @@ -471,6 +473,8 @@ const translations: Record = { headerDisplayMonth: "Monatsname", headerDisplayMonthYear: "Monat & Jahr", headerDisplayDate: "Vollständiges Datum", + headerDisplayCustom: "Benutzerdefiniert", + headerCustomFormatLabel: "Format (z.B. DD.MM.YYYY)", listView: "Liste", notes: "Notizen", notesSidebar: "Notizen-Seitenleiste", @@ -682,6 +686,8 @@ const translations: Record = { headerDisplayMonth: "Nom du mois", headerDisplayMonthYear: "Mois & Année", headerDisplayDate: "Date complète", + headerDisplayCustom: "Personnalisé", + headerCustomFormatLabel: "Format (ex: DD.MM.YYYY)", language: "Langue", dateFormat: "Format de date", timeFormat: "Format d'heure", @@ -891,6 +897,8 @@ const translations: Record = { headerDisplayMonth: "Nombre del mes", headerDisplayMonthYear: "Mes y Año", headerDisplayDate: "Fecha completa", + headerDisplayCustom: "Personalizado", + headerCustomFormatLabel: "Formato (ej. DD.MM.YYYY)", language: "Idioma", dateFormat: "Formato de fecha", timeFormat: "Formato de hora", @@ -1100,6 +1108,8 @@ const translations: Record = { headerDisplayMonth: "Nome del mese", headerDisplayMonthYear: "Mese e Anno", headerDisplayDate: "Data completa", + headerDisplayCustom: "Personalizzato", + headerCustomFormatLabel: "Formato (es. DD.MM.YYYY)", language: "Lingua", dateFormat: "Formato data", timeFormat: "Formato ora", @@ -1424,6 +1434,37 @@ function getCWReferenceDate(days: Date[]): Date { return new Date(first.getTime() - distToMonday * 86400000); } +// Format a custom header string using tokens +function formatCustomHeader(format: string, days: Date[], language: string, t: any): string { + if (!format) return ""; + const refDate = getCWReferenceDate(days); + const today = new Date(); + + let result = format; + + // [TODAY] token + result = result.replace(/\[TODAY\]/g, today.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' })); + + // YYYY + result = result.replace(/YYYY/g, refDate.getFullYear().toString()); + + // WW (ISO Week) + result = result.replace(/WW/g, getWeekNumber(refDate).toString().padStart(2, '0')); + result = result.replace(/W/g, getWeekNumber(refDate).toString()); + + // MMMM (Month Name Long) + result = result.replace(/MMMM/g, refDate.toLocaleDateString(language, { month: 'long' })); + result = result.replace(/MMM/g, refDate.toLocaleDateString(language, { month: 'short' })); + result = result.replace(/MM/g, (refDate.getMonth() + 1).toString().padStart(2, '0')); + result = result.replace(/M/g, (refDate.getMonth() + 1).toString()); + + // DD (Day) + result = result.replace(/DD/g, refDate.getDate().toString().padStart(2, '0')); + result = result.replace(/D/g, refDate.getDate().toString()); + + return result; +} + // Check if an event is an all-day event // Defined outside component to avoid stale closure issues in useCallbacks const isAllDayEvent = (event: CalendarEvent): boolean => { @@ -1803,7 +1844,8 @@ export default function WeeklyView() { weekdayCase?: "normal" | "capitalize" | "uppercase"; customWeekdayNames?: string; dateVerticalAlign?: "top" | "middle" | "bottom"; - headerDisplay?: "kw" | "month" | "month_year" | "date"; + headerDisplay?: "kw" | "month" | "month_year" | "date" | "custom"; + headerCustomFormat?: string; lightTheme?: any; darkTheme?: any; }>({ @@ -5474,6 +5516,8 @@ export default function WeeklyView() { {getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' })} ) : profile.headerDisplay === "month_year" ? ( {getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' })} + ) : profile.headerDisplay === "custom" ? ( + {formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)} ) : ( <> KW{getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")} @@ -5731,7 +5775,9 @@ export default function WeeklyView() { ? getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long", year: "numeric" }) : profile.headerDisplay === "date" ? getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' }) - : `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}` + : profile.headerDisplay === "custom" + ? formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t) + : `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}` } {(profile.headerDisplay === "kw" || profile.headerDisplay === "month" || !profile.headerDisplay) && ( @@ -9694,7 +9740,8 @@ function SettingsSidebar({ weekdayCase?: "normal" | "capitalize" | "uppercase"; customWeekdayNames?: string; dateVerticalAlign?: "top" | "middle" | "bottom"; - headerDisplay?: "kw" | "month" | "month_year" | "date"; + headerDisplay?: "kw" | "month" | "month_year" | "date" | "custom"; + headerCustomFormat?: string; }>({ name: "", email: "", @@ -9709,6 +9756,7 @@ function SettingsSidebar({ focusTimerDuration: 25, focusBreakDuration: 5, headerDisplay: "kw", + headerCustomFormat: "KW WW | YYYY", cellDuration: 30, viewStyle: "list", fontSize: "M", @@ -10852,7 +10900,37 @@ function SettingsSidebar({ > {t.headerDisplayDate} + + {profile.headerDisplay === "custom" && ( +
+ + { + const val = e.target.value; + setProfile({ ...profile, headerCustomFormat: val }); + saveSetting("headerCustomFormat", val); + }} + placeholder="KW WW | YYYY or DD.MM.YYYY" + className="w-full px-3 py-1.5 text-sm rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white" + /> +
+ Tokens: WW (Week), YYYY (Year), MMMM (Month Name), MM (Month Num), DD (Day), [TODAY] (Active Date) +
+
+ )} {/* Kanban Stages Settings */}