From 0b40974b886929ca3414c910ff4b992a0e893094 Mon Sep 17 00:00:00 2001 From: mARTin Date: Tue, 7 Apr 2026 10:10:37 +0200 Subject: [PATCH] feat: persist sidebar toggle settings per-device via cookies The sidebar eye-toggles (Someday, All-day, Checkboxes, Project Icons, Weather) and the sub-hour slots toggle now save to a device-local cookie instead of the DB, so phone and desktop can have independent display preferences. - DEVICE_SETTINGS_KEYS extended with showSubHourSlots - New DEVICE_VIEW_SETTINGS_KEYS for per-view sidebar toggles (showSomeday, showAllDayEvents, showTaskCheckboxes, showProjectIcons, weatherEnabled) - saveViewSetting writes cookie-only for device keys; DB untouched - getEffective checks device cookie before DB per-view overrides - Cookie restores applied after DB load in both fetchProfile and fetchUserInfo so device preference always wins v1.94.0 --- package.json | 2 +- src/components/WeeklyView.tsx | 61 ++++++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index dc8f1b2..27baba3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.93.1", + "version": "1.94.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 5bc8fdd..3707f0b 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -110,7 +110,12 @@ const RichTextEditor = dynamic(() => import("./RichTextEditor"), { ssr: false }) const PriorityView = dynamic(() => import("./PriorityView"), { ssr: false }); // Cookie helpers for per-device settings -const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour", "fontSize"]; +// Keys here bypass the DB and save/load from cookie only — each device keeps its own value +const DEVICE_SETTINGS_KEYS = ["viewDays", "cellDuration", "startHour", "endHour", "fontSize", "showSubHourSlots"]; + +// Per-view toggle keys that are also device-specific (sidebar eye toggles) +const DEVICE_VIEW_SETTINGS_KEYS = ["showSomeday", "showAllDayEvents", "showTaskCheckboxes", "showProjectIcons", "weatherEnabled"] as const; +type DeviceViewSettingKey = typeof DEVICE_VIEW_SETTINGS_KEYS[number]; function getCookie(name: string): string | null { if (typeof document === "undefined") return null; @@ -124,6 +129,16 @@ function setCookie(name: string, value: string, days: number = 365) { document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`; } +function readDeviceViewCookie(): Record> { + const raw = getCookie("device_view_settings"); + if (!raw) return {}; + try { return JSON.parse(raw); } catch { return {}; } +} + +function writeDeviceViewCookie(settings: Record>) { + setCookie("device_view_settings", JSON.stringify(settings)); +} + export type ViewStyle = "simple" | "calendar" | "list" | "grid" | "kanban" | "priority"; export interface KanbanStage { @@ -611,7 +626,10 @@ export default function WeeklyView() { const [timeFormat, setTimeFormat] = useState("24h"); const [dateFormat, setDateFormat] = useState("yyyy-MM-dd"); const [hourLabelFormat, setHourLabelFormat] = useState<"short" | "full">("short"); - const [showSubHourSlots, setShowSubHourSlots] = useState(true); + const [showSubHourSlots, setShowSubHourSlots] = useState(() => { + const c = getCookie("setting_showSubHourSlots"); + return c !== null ? c === "true" : true; + }); const [allDayPosition, setAllDayPosition] = useState<"above" | "below">("below"); const [somedayExpanded, setSomedayExpanded] = useState(true); @@ -1023,7 +1041,17 @@ export default function WeeklyView() { const viewSettingsRef = useRef>({}); viewSettingsRef.current = viewSettings; + // Device-local per-view overrides (cookie-based, not synced to DB) + const [deviceViewSettings, setDeviceViewSettings] = useState>>(() => readDeviceViewCookie()); + const deviceViewSettingsRef = useRef>>({}); + deviceViewSettingsRef.current = deviceViewSettings; + const getEffective = (key: K, globalVal: PerViewOverrides[K]): PerViewOverrides[K] => { + // Device-specific settings take priority (cookie-based, not synced across devices) + if ((DEVICE_VIEW_SETTINGS_KEYS as readonly string[]).includes(key)) { + const dvs = deviceViewSettingsRef.current[profile.viewStyle]; + if (dvs && dvs[key] !== undefined) return dvs[key] as PerViewOverrides[K]; + } const vs = viewSettingsRef.current[profile.viewStyle]; if (vs && vs[key] !== undefined) return vs[key] as PerViewOverrides[K]; return globalVal; @@ -1033,6 +1061,15 @@ export default function WeeklyView() { return !!(vs && vs[key] !== undefined); }; const saveViewSetting = async (key: K, value: PerViewOverrides[K], perView: boolean) => { + // Device-specific keys: save to cookie only, not DB + if ((DEVICE_VIEW_SETTINGS_KEYS as readonly string[]).includes(key) && perView) { + const dvs = { ...deviceViewSettingsRef.current }; + dvs[profile.viewStyle] = { ...(dvs[profile.viewStyle] || {}), [key]: value }; + deviceViewSettingsRef.current = dvs; + setDeviceViewSettings(dvs); + writeDeviceViewCookie(dvs); + return; + } const updated = { ...viewSettingsRef.current }; if (perView) { updated[profile.viewStyle] = { ...(updated[profile.viewStyle] || {}), [key]: value }; @@ -1359,6 +1396,18 @@ export default function WeeklyView() { } } + // Re-apply per-device cookie overrides — these always win over DB values + const _cFS = getCookie("setting_fontSize"); + if (_cFS) setFontSize(_cFS as "S" | "M" | "L"); + const _cSH = getCookie("setting_showSubHourSlots"); + if (_cSH !== null) setShowSubHourSlots(_cSH === "true"); + const _cCD = getCookie("setting_cellDuration"); + if (_cCD) setCellDuration(Number(_cCD) as CellDuration); + const _cStart = getCookie("setting_startHour"); + if (_cStart) setStartHour(Number(_cStart)); + const _cEnd = getCookie("setting_endHour"); + if (_cEnd) setEndHour(Number(_cEnd)); + // Show onboarding wizard for new users if (profileData.hasCompletedOnboarding === false) { setShowOnboarding(true); @@ -2349,12 +2398,13 @@ export default function WeeklyView() { if (cookieStartHour) setStartHour(Number(cookieStartHour)); const cookieEndHour = getCookie("setting_endHour"); if (cookieEndHour) setEndHour(Number(cookieEndHour)); - const cookieFontSize = getCookie("setting_fontSize"); - if (cookieFontSize) setFontSize(cookieFontSize as "S" | "M" | "L"); setShowNextTask(data.user.showNextTask || false); setCalendarEditMode(data.user.calendarEditMode || false); if (data.user.fontSize) setFontSize(data.user.fontSize as "S" | "M" | "L"); + // Cookie wins over DB for per-device settings + const cookieFontSize2 = getCookie("setting_fontSize"); + if (cookieFontSize2) setFontSize(cookieFontSize2 as "S" | "M" | "L"); if (data.user.showSomeday !== undefined) setShowSomeday(data.user.showSomeday); @@ -2372,6 +2422,9 @@ export default function WeeklyView() { setHourLabelFormat(data.user.hourLabelFormat as "short" | "full"); if (data.user.showSubHourSlots !== undefined) setShowSubHourSlots(data.user.showSubHourSlots); + // Cookie wins over DB for showSubHourSlots (per-device) + const _cSH2 = getCookie("setting_showSubHourSlots"); + if (_cSH2 !== null) setShowSubHourSlots(_cSH2 === "true"); if (data.user.allDayPosition) setAllDayPosition(data.user.allDayPosition as "above" | "below"); if (data.user.viewSettings) setViewSettings(data.user.viewSettings);