From b3503d9e4d542fd4788d29c89cb769ae639da174 Mon Sep 17 00:00:00 2001 From: mARTin Date: Wed, 4 Mar 2026 02:49:44 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20UI=20improvements=20A-F=20=E2=80=94=20c?= =?UTF-8?q?ookies,=20KW=20fix,=20mobile=20header,=20indicators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/globals.css | 15 +++++++- src/components/WeeklyView.tsx | 70 +++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 6b1e3d8..43a6d27 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2757,7 +2757,7 @@ h3 { /* Week Grid Transition Group */ ::view-transition-group(week-grid) { - animation-duration: 0.3s; + animation-duration: 0.9s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; } @@ -3202,6 +3202,19 @@ h3 { .weekly-logo { font-size: 1rem; } + + /* D) Mobile: sticky day header at top so user always knows which day */ + .weekly-day-header { + position: sticky; + top: 0; + z-index: 10; + background: var(--weekly-bg, white); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + } + + .dark-mode .weekly-day-header { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + } } diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index d484174..479a477 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -55,6 +55,21 @@ import RecurringTasksManager from "./RecurringTasksManager"; export interface RecurringTaskException { id: string; taskId: string; originalDate: string; newDate?: string | null; isCancelled: boolean; createdAt: Date; updatedAt: Date; } import { ImportListModal } from "./ImportListModal"; +// Cookie helpers for per-device settings +const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour"]; + +function getCookie(name: string): string | null { + if (typeof document === "undefined") return null; + const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)); + return match ? decodeURIComponent(match[1]) : null; +} + +function setCookie(name: string, value: string, days: number = 365) { + if (typeof document === "undefined") return; + const expires = new Date(Date.now() + days * 864e5).toUTCString(); + document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`; +} + export type ViewStyle = "simple" | "calendar" | "list" | "grid"; export interface Task { @@ -1393,6 +1408,10 @@ export default function WeeklyView() { return () => el.removeEventListener("wheel", handler); }, [showSomeday, somedayExpanded]); const saveSetting = async (key: string, value: any) => { + // Save per-device settings to cookie as well + if (DEVICE_SETTINGS_KEYS.includes(key)) { + setCookie(`setting_${key}`, String(value)); + } try { await fetch("/api/user/profile", { method: "PATCH", @@ -1469,24 +1488,34 @@ export default function WeeklyView() { setTimeFormat(data.user.timeFormat || "12h"); setDateFormat(data.user.dateFormat || "MM/dd/yyyy"); setLanguage(data.user.language || "en"); - if (data.user.startHour !== undefined) - setStartHour(data.user.startHour); - if (data.user.endHour !== undefined) setEndHour(data.user.endHour); + if (data.user.startHour !== undefined) { + const cookieStartHour = getCookie("setting_startHour"); + setStartHour(cookieStartHour ? Number(cookieStartHour) : data.user.startHour); + } + if (data.user.endHour !== undefined) { + const cookieEndHour = getCookie("setting_endHour"); + setEndHour(cookieEndHour ? Number(cookieEndHour) : data.user.endHour); + } if (data.user.viewStyle !== undefined) { setViewStyle(data.user.viewStyle as ViewStyle); setShowTimeGrid(data.user.showTimeGrid ?? true); } if (data.user.viewDays !== undefined) { - savedViewDaysRef.current = data.user.viewDays; + // Cookie override for per-device viewDays + const cookieViewDays = getCookie("setting_viewDays"); + const effectiveViewDays = cookieViewDays ? Number(cookieViewDays) : data.user.viewDays; + savedViewDaysRef.current = effectiveViewDays; // Re-apply responsive constraints after loading saved preference const width = window.innerWidth; if (width <= 480) setViewDays(1); else if (width <= 768) setViewDays(3); - else if (width <= 1024) setViewDays(Math.min(data.user.viewDays, 5)); - else setViewDays(data.user.viewDays); + else if (width <= 1024) setViewDays(Math.min(effectiveViewDays, 5)); + else setViewDays(effectiveViewDays); + } + if (data.user.cellDuration !== undefined) { + const cookieCellDuration = getCookie("setting_cellDuration"); + setCellDuration((cookieCellDuration ? Number(cookieCellDuration) : data.user.cellDuration) as CellDuration); } - if (data.user.cellDuration !== undefined) - setCellDuration(data.user.cellDuration as CellDuration); setShowNextTask(data.user.showNextTask || false); setCalendarEditMode(data.user.calendarEditMode || false); if (data.user.fontSize) @@ -3709,7 +3738,9 @@ export default function WeeklyView() { ) : syncError ? ( ) : null} - KW {getWeekNumber(currentWeekStart).toString().padStart(2, "0")} + KW {getWeekNumber((() => { const days = getVisibleDays(); const mid = days[Math.floor(days.length / 2)] || currentWeekStart; return mid; })()).toString().padStart(2, "0")} + | + {(() => { const days = getVisibleDays(); const mid = days[Math.floor(days.length / 2)] || currentWeekStart; return mid; })().getFullYear()} {/* Right: Settings + Overflow */} @@ -3920,7 +3951,7 @@ export default function WeeklyView() { color: adjustColorForDarkMode(profile.cwColor || "#333333", darkMode), filter: "brightness(var(--weekly-header-brightness, 1))" }}> - KW {getWeekNumber(currentWeekStart).toString().padStart(2, "0")} + KW {getWeekNumber((() => { const days = getVisibleDays(); const mid = days[Math.floor(days.length / 2)] || currentWeekStart; return mid; })()).toString().padStart(2, "0")} | )} + + {task.title} + {task.subTasks && task.subTasks.length > 0 && !isSubTask && (