diff --git a/package.json b/package.json
index ad978f3..89a4bb9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
- "version": "1.80.4",
+ "version": "1.81.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/prisma/schema.prisma b/prisma/schema.prisma
index 422da7e..76fb73e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -80,7 +80,10 @@ model User {
dateAlignment String @default("center")
dateVerticalAlign String? @default("middle")
headerDisplay String? @default("kw")
- headerCustomFormat String? @default("KW WW | YYYY")
+ headerCustomFormat String? @default("KW WW | YYYY")
+ headerCurrentDayFormat String? @default("DDD, DD. MMMM YYYY")
+ mobilePortraitHeaderDisplay String? @default("current_day")
+ mobileLandscapeHeaderDisplay String? @default("kw")
hourLabelFormat String @default("short")
showSubHourSlots Boolean @default(true)
allDayPosition String @default("above")
diff --git a/src/app/api/user/profile/route.ts b/src/app/api/user/profile/route.ts
index 1398490..4f9b142 100644
--- a/src/app/api/user/profile/route.ts
+++ b/src/app/api/user/profile/route.ts
@@ -48,6 +48,9 @@ export async function GET(request: NextRequest) {
dateVerticalAlign: true,
headerDisplay: true,
headerCustomFormat: true,
+ headerCurrentDayFormat: true,
+ mobilePortraitHeaderDisplay: true,
+ mobileLandscapeHeaderDisplay: true,
headlineFont: true,
headlineFontSize: true,
headlineFontWeight: true,
@@ -141,7 +144,7 @@ export async function PATCH(request: NextRequest) {
weekdayColor, dateColor, taskColor, todayHighlightColor,
pastDayColor, goalFallbackType, goalDefaultSentence,
goalFontFamily, goalFontSize, goalFontWeight, goalScope,
- dateLayout, mobileDateLayout, dateVerticalAlign, headerDisplay, headerCustomFormat,
+ dateLayout, mobileDateLayout, dateVerticalAlign, headerDisplay, headerCustomFormat, headerCurrentDayFormat, mobilePortraitHeaderDisplay, mobileLandscapeHeaderDisplay,
hourLabelFormat, showSubHourSlots, allDayPosition,
cwFontFamily, cwFontSize, cwFontWeight, cwColor,
yearFontFamily, yearFontSize, yearFontWeight, yearColor,
@@ -211,6 +214,9 @@ export async function PATCH(request: NextRequest) {
...(dateVerticalAlign !== undefined && { dateVerticalAlign }),
...(headerDisplay !== undefined && { headerDisplay }),
...(headerCustomFormat !== undefined && { headerCustomFormat }),
+ ...(headerCurrentDayFormat !== undefined && { headerCurrentDayFormat }),
+ ...(mobilePortraitHeaderDisplay !== undefined && { mobilePortraitHeaderDisplay }),
+ ...(mobileLandscapeHeaderDisplay !== undefined && { mobileLandscapeHeaderDisplay }),
...(hourLabelFormat !== undefined && { hourLabelFormat }),
...(showSubHourSlots !== undefined && { showSubHourSlots }),
...(allDayPosition !== undefined && { allDayPosition }),
@@ -314,6 +320,9 @@ export async function PATCH(request: NextRequest) {
dateVerticalAlign: true,
headerDisplay: true,
headerCustomFormat: true,
+ headerCurrentDayFormat: true,
+ mobilePortraitHeaderDisplay: true,
+ mobileLandscapeHeaderDisplay: true,
cwFontFamily: true,
cwFontSize: true,
cwFontWeight: true,
diff --git a/src/components/SettingsSidebar.tsx b/src/components/SettingsSidebar.tsx
index b7d43f4..6ceb66d 100644
--- a/src/components/SettingsSidebar.tsx
+++ b/src/components/SettingsSidebar.tsx
@@ -900,6 +900,27 @@ function SettingsSidebar({
+ {profile.headerDisplay === "current_day" && (
+
+
+
{
+ const val = e.target.value;
+ setProfile({ ...profile, headerCurrentDayFormat: val });
+ saveSetting("headerCurrentDayFormat", val);
+ }}
+ placeholder="DDD, DD. MMMM 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: DDDD (Montag), DDD (Mo.), DD (30), MMMM (März), MMM (Mär), MM (03), YYYY (2026)
+
+
+ )}
{profile.headerDisplay === "custom" && (
)}
+ {/* Mobile Portrait/Landscape overrides */}
+
+
+
+
+
+
+
+
+
+
{/* Push Notifications */}
diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx
index 908edd3..a14ac30 100644
--- a/src/components/WeeklyView.tsx
+++ b/src/components/WeeklyView.tsx
@@ -380,9 +380,9 @@ function getCWReferenceDate(days: Date[]): Date {
}
// Format a custom header string using tokens
-function formatCustomHeader(format: string, days: Date[], language: string, t: any): string {
+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();
@@ -391,7 +391,7 @@ function formatCustomHeader(format: string, days: Date[], language: string, t: a
d.getMonth() === today.getMonth() &&
d.getFullYear() === today.getFullYear()
);
- const refDate = isTodayInWeek ? today : getCWReferenceDate(days);
+ const refDate = refDateOverride ?? (isTodayInWeek ? today : getCWReferenceDate(days));
// Define token mappings
const tokens: Record = {
@@ -415,6 +415,16 @@ function formatCustomHeader(format: string, days: Date[], language: string, t: a
return format.replace(regex, (match) => tokens[match] || match);
}
+// Get the "selected" day for current_day header: today if visible, otherwise first visible day
+function getSelectedDay(days: Date[]): Date {
+ 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
const isAllDayEvent = (event: CalendarEvent): boolean => {
@@ -5603,33 +5613,22 @@ export default function WeeklyView() {
)}
{syncError && }
- {isPortrait ? (() => {
- const shownDay = getVisibleDays()[0];
- return shownDay.toLocaleDateString(profile.language || "de-DE", { weekday: "short", day: "2-digit", month: "long", year: "numeric" });
- })() :
- profile.headerDisplay === "none" ? "" :
- profile.headerDisplay === "current_day" ? (() => {
- const shownDay = getVisibleDays()[0];
- return shownDay.toLocaleDateString(profile.language || "de-DE", { weekday: "short", day: "2-digit", month: "long", year: "numeric" });
- })() :
- profile.headerDisplay === "date" ? (() => {
- const today = new Date();
- const isTodayInWeek = getVisibleDays().some(d =>
- d.getDate() === today.getDate() &&
- d.getMonth() === today.getMonth() &&
- d.getFullYear() === today.getFullYear()
- );
- const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
- return refDate.toLocaleDateString(profile.language, { day: '2-digit', month: '2-digit', year: 'numeric' });
- })() :
- profile.headerDisplay === "month_year" ?
- getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: 'long', year: 'numeric' }) :
- profile.headerDisplay === "custom" ?
- formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), profile.language, t) :
- profile.headerDisplay === "month" ?
- getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: "long" }) :
- `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")} | ${getCWReferenceDate(getVisibleDays()).getFullYear()}`
- }
+ {(() => {
+ const visibleDays = getVisibleDays();
+ const mobileDisplay = isPortrait
+ ? (profile.mobilePortraitHeaderDisplay || "current_day")
+ : (profile.mobileLandscapeHeaderDisplay || profile.headerDisplay || "kw");
+ if (mobileDisplay === "none") return "";
+ if (mobileDisplay === "current_day") {
+ const fmt = profile.headerCurrentDayFormat || "DDD, DD. MMMM YYYY";
+ return formatCustomHeader(fmt, visibleDays, profile.language, t, getSelectedDay(visibleDays));
+ }
+ if (mobileDisplay === "date") return new Date().toLocaleDateString(profile.language, { day: '2-digit', month: '2-digit', year: 'numeric' });
+ if (mobileDisplay === "month_year") return getCWReferenceDate(visibleDays).toLocaleDateString(profile.language, { month: 'long', year: 'numeric' });
+ if (mobileDisplay === "month") return getCWReferenceDate(visibleDays).toLocaleDateString(profile.language, { month: "long" });
+ if (mobileDisplay === "custom") return formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", visibleDays, profile.language, t);
+ return `KW ${getWeekNumber(getCWReferenceDate(visibleDays)).toString().padStart(2, "0")} | ${getCWReferenceDate(visibleDays).getFullYear()}`;
+ })()}
@@ -5763,35 +5762,21 @@ export default function WeeklyView() {
filter: "brightness(var(--weekly-header-brightness, 1))"
}}>
{(() => {
- const effectiveDisplay = profile.viewStyle === "kanban" && !profile.headerDisplay ? "current_day" : (profile.headerDisplay || "kw");
+ const effectiveDisplay = (profile.viewStyle === "kanban" && !profile.headerDisplay) ? "current_day" : (profile.headerDisplay || "kw");
+ const visibleDays = getVisibleDays();
if (effectiveDisplay === "none") return "";
if (effectiveDisplay === "current_day") {
- const today = new Date();
- const visibleDays = getVisibleDays();
- const shownDay = visibleDays.some(d =>
- d.getDate() === today.getDate() &&
- d.getMonth() === today.getMonth() &&
- d.getFullYear() === today.getFullYear()
- ) ? today : visibleDays[0];
- return shownDay.toLocaleDateString(profile.language || "de-DE", { weekday: "short", day: "2-digit", month: "long", year: "numeric" });
+ const fmt = profile.headerCurrentDayFormat || "DDD, DD. MMMM YYYY";
+ return formatCustomHeader(fmt, visibleDays, profile.language, t, getSelectedDay(visibleDays));
}
- if (effectiveDisplay === "month") return getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: "long" });
- if (effectiveDisplay === "month_year") return getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: "long", year: "numeric" });
- if (effectiveDisplay === "date") {
- const today = new Date();
- const isTodayInWeek = getVisibleDays().some(d =>
- d.getDate() === today.getDate() &&
- d.getMonth() === today.getMonth() &&
- d.getFullYear() === today.getFullYear()
- );
- const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
- return refDate.toLocaleDateString(profile.language, { day: '2-digit', month: '2-digit', year: 'numeric' });
- }
- if (effectiveDisplay === "custom") return formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), profile.language, t);
- return `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}`;
+ if (effectiveDisplay === "month") return getCWReferenceDate(visibleDays).toLocaleDateString(profile.language, { month: "long" });
+ if (effectiveDisplay === "month_year") return getCWReferenceDate(visibleDays).toLocaleDateString(profile.language, { month: "long", year: "numeric" });
+ if (effectiveDisplay === "date") return new Date().toLocaleDateString(profile.language, { day: '2-digit', month: '2-digit', year: 'numeric' });
+ if (effectiveDisplay === "custom") return formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", visibleDays, profile.language, t);
+ return `KW ${getWeekNumber(getCWReferenceDate(visibleDays)).toString().padStart(2, "0")}`;
})()}
- {profile.headerDisplay !== "none" && profile.headerDisplay !== "current_day" && (profile.headerDisplay === "kw" || profile.headerDisplay === "month" || !profile.headerDisplay) && (
+ {profile.headerDisplay !== "none" && profile.headerDisplay !== "current_day" && profile.headerDisplay !== "date" && profile.headerDisplay !== "month_year" && (profile.headerDisplay === "kw" || profile.headerDisplay === "month" || !profile.headerDisplay) && (
<>
|
)}
+ {/* Refresh / sync status — fixed-width slot so the date never shifts */}
+
+ {syncError ? (
+
+ ) : (isLoading || isSyncing || syncStatus === "syncing") ? (
+
+ ) : (
+
+ )}
+
{/* Goal — hidden on tablet to save space */}
- {syncError ? (
-
- ) : (isLoading || isSyncing || syncStatus === "syncing") ? (
-
- ) : (
-
- )}
{isEditingGoal ? (
= {
noStage: "No stage",
headerDisplay: "Header Display",
headerDisplayKW: "Calendar Week (KW)",
- headerDisplayMonth: "Month Name - March",
- headerDisplayMonthYear: "Month & Year - March | 2026",
- headerDisplayDate: "Full Date - 13.03.2026",
- headerDisplayCustom: "Custom - Friday - 13. March",
- headerDisplayCurrentDay: "Current Day - Friday, 13. March 2026",
+ headerDisplayMonth: "Month Name — March",
+ headerDisplayMonthYear: "Month & Year — March | 2026",
+ headerDisplayDate: "Today's Date — 30.03.2026",
+ headerDisplayCurrentDay: "Selected Day — Mo., 30. March 2026",
+ headerDisplayCustom: "Custom Week Format",
headerDisplayNone: "None",
- headerCustomFormatLabel: "Format string (e.g. DD.MM.YYYY)",
+ headerCustomFormatLabel: "Week format (e.g. KW WW | YYYY)",
+ headerCurrentDayFormatLabel: "Day format (e.g. DDD, DD. MMMM YYYY)",
+ headerMobilePortrait: "Mobile Portrait",
+ headerMobileLandscape: "Mobile Landscape",
language: "Language",
dateFormat: "Date Format",
timeFormat: "Time Format",
@@ -270,13 +273,16 @@ export const translations: Record
= {
noStage: "Keine Phase",
headerDisplay: "Kopfzeile",
headerDisplayKW: "Kalenderwoche (KW)",
- headerDisplayMonth: "Monatsname - März",
- headerDisplayMonthYear: "Monat & Jahr - März | 2026",
- headerDisplayDate: "Vollständiges Datum - 13.03.2026",
- headerDisplayCustom: "Benutzerdefiniert - Freitag - 13. März",
- headerDisplayCurrentDay: "Aktueller Tag - Freitag, 13. März 2026",
+ headerDisplayMonth: "Monatsname — März",
+ headerDisplayMonthYear: "Monat & Jahr — März | 2026",
+ headerDisplayDate: "Heutiges Datum — 30.03.2026",
+ headerDisplayCurrentDay: "Selektierter Tag — Mo., 30. März 2026",
+ headerDisplayCustom: "Woche Benutzerdefiniert",
headerDisplayNone: "Nichts",
- headerCustomFormatLabel: "Format (z.B. DD.MM.YYYY)",
+ headerCustomFormatLabel: "Wochenformat (z.B. KW WW | YYYY)",
+ headerCurrentDayFormatLabel: "Tagesformat (z.B. DDD, DD. MMMM YYYY)",
+ headerMobilePortrait: "Mobile Hochformat",
+ headerMobileLandscape: "Mobile Querformat",
listView: "Liste",
notes: "Notizen",
notesSidebar: "Notizen-Seitenleiste",
@@ -517,11 +523,14 @@ export const translations: Record = {
headerDisplayKW: "Semaine calendaire (KW)",
headerDisplayMonth: "Nom du mois - Mars",
headerDisplayMonthYear: "Mois & Année - Mars | 2026",
- headerDisplayDate: "Date complète - 13.03.2026",
- headerDisplayCustom: "Personnalisé - Vendredi - 13 Mars",
- headerDisplayCurrentDay: "Jour actuel - Vendredi, 13 Mars 2026",
+ headerDisplayDate: "Date du jour — 30.03.2026",
+ headerDisplayCurrentDay: "Jour sélectionné — Lu., 30 Mars 2026",
+ headerDisplayCustom: "Semaine personnalisée",
headerDisplayNone: "Aucun",
- headerCustomFormatLabel: "Format (ex: DD.MM.YYYY)",
+ headerCustomFormatLabel: "Format semaine (ex: KW WW | YYYY)",
+ headerCurrentDayFormatLabel: "Format jour (ex: DDD, DD. MMMM YYYY)",
+ headerMobilePortrait: "Mobile Portrait",
+ headerMobileLandscape: "Mobile Paysage",
language: "Langue",
dateFormat: "Format de date",
timeFormat: "Format d'heure",
@@ -735,11 +744,14 @@ export const translations: Record = {
headerDisplayKW: "Semana calendario (KW)",
headerDisplayMonth: "Nombre del mes - Marzo",
headerDisplayMonthYear: "Mes y Año - Marzo | 2026",
- headerDisplayDate: "Fecha completa - 13.03.2026",
- headerDisplayCustom: "Personalizado - Viernes - 13 Marzo",
- headerDisplayCurrentDay: "Día actual - Viernes, 13 Marzo 2026",
+ headerDisplayDate: "Fecha de hoy — 30.03.2026",
+ headerDisplayCurrentDay: "Día seleccionado — Lu., 30 Marzo 2026",
+ headerDisplayCustom: "Semana personalizada",
headerDisplayNone: "Ninguno",
- headerCustomFormatLabel: "Formato (ej. DD.MM.YYYY)",
+ headerCustomFormatLabel: "Formato semana (ej. KW WW | YYYY)",
+ headerCurrentDayFormatLabel: "Formato día (ej. DDD, DD. MMMM YYYY)",
+ headerMobilePortrait: "Móvil Retrato",
+ headerMobileLandscape: "Móvil Paisaje",
language: "Idioma",
dateFormat: "Formato de fecha",
timeFormat: "Formato de hora",
@@ -953,11 +965,14 @@ export const translations: Record = {
headerDisplayKW: "Settimana calendario (KW)",
headerDisplayMonth: "Nome del mese - Marzo",
headerDisplayMonthYear: "Mese e Anno - Marzo | 2026",
- headerDisplayDate: "Data completa - 13.03.2026",
- headerDisplayCustom: "Personalizzato - Venerdì - 13 Marzo",
- headerDisplayCurrentDay: "Giorno corrente - Venerdì, 13 Marzo 2026",
+ headerDisplayDate: "Data di oggi — 30.03.2026",
+ headerDisplayCurrentDay: "Giorno selezionato — Lu., 30 Marzo 2026",
+ headerDisplayCustom: "Settimana personalizzata",
headerDisplayNone: "Nessuno",
- headerCustomFormatLabel: "Formato (es. DD.MM.YYYY)",
+ headerCustomFormatLabel: "Formato settimana (es. KW WW | YYYY)",
+ headerCurrentDayFormatLabel: "Formato giorno (es. DDD, DD. MMMM YYYY)",
+ headerMobilePortrait: "Mobile Verticale",
+ headerMobileLandscape: "Mobile Orizzontale",
language: "Lingua",
dateFormat: "Formato data",
timeFormat: "Formato ora",