feat: UI improvements A-F — cookies, KW fix, mobile header, indicators

This commit is contained in:
mARTin 2026-03-04 02:49:44 +01:00
parent 9be39b0b30
commit b3503d9e4d
2 changed files with 65 additions and 20 deletions

View File

@ -2757,7 +2757,7 @@ h3 {
/* Week Grid Transition Group */ /* Week Grid Transition Group */
::view-transition-group(week-grid) { ::view-transition-group(week-grid) {
animation-duration: 0.3s; animation-duration: 0.9s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden; overflow: hidden;
} }
@ -3202,6 +3202,19 @@ h3 {
.weekly-logo { .weekly-logo {
font-size: 1rem; 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);
}
} }

View File

@ -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; } export interface RecurringTaskException { id: string; taskId: string; originalDate: string; newDate?: string | null; isCancelled: boolean; createdAt: Date; updatedAt: Date; }
import { ImportListModal } from "./ImportListModal"; 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 type ViewStyle = "simple" | "calendar" | "list" | "grid";
export interface Task { export interface Task {
@ -1393,6 +1408,10 @@ export default function WeeklyView() {
return () => el.removeEventListener("wheel", handler); return () => el.removeEventListener("wheel", handler);
}, [showSomeday, somedayExpanded]); }, [showSomeday, somedayExpanded]);
const saveSetting = async (key: string, value: any) => { 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 { try {
await fetch("/api/user/profile", { await fetch("/api/user/profile", {
method: "PATCH", method: "PATCH",
@ -1469,24 +1488,34 @@ export default function WeeklyView() {
setTimeFormat(data.user.timeFormat || "12h"); setTimeFormat(data.user.timeFormat || "12h");
setDateFormat(data.user.dateFormat || "MM/dd/yyyy"); setDateFormat(data.user.dateFormat || "MM/dd/yyyy");
setLanguage(data.user.language || "en"); setLanguage(data.user.language || "en");
if (data.user.startHour !== undefined) if (data.user.startHour !== undefined) {
setStartHour(data.user.startHour); const cookieStartHour = getCookie("setting_startHour");
if (data.user.endHour !== undefined) setEndHour(data.user.endHour); 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) { if (data.user.viewStyle !== undefined) {
setViewStyle(data.user.viewStyle as ViewStyle); setViewStyle(data.user.viewStyle as ViewStyle);
setShowTimeGrid(data.user.showTimeGrid ?? true); setShowTimeGrid(data.user.showTimeGrid ?? true);
} }
if (data.user.viewDays !== undefined) { 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 // Re-apply responsive constraints after loading saved preference
const width = window.innerWidth; const width = window.innerWidth;
if (width <= 480) setViewDays(1); if (width <= 480) setViewDays(1);
else if (width <= 768) setViewDays(3); else if (width <= 768) setViewDays(3);
else if (width <= 1024) setViewDays(Math.min(data.user.viewDays, 5)); else if (width <= 1024) setViewDays(Math.min(effectiveViewDays, 5));
else setViewDays(data.user.viewDays); 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); setShowNextTask(data.user.showNextTask || false);
setCalendarEditMode(data.user.calendarEditMode || false); setCalendarEditMode(data.user.calendarEditMode || false);
if (data.user.fontSize) if (data.user.fontSize)
@ -3709,7 +3738,9 @@ export default function WeeklyView() {
) : syncError ? ( ) : syncError ? (
<AlertCircle size={14} className="text-red-500" /> <AlertCircle size={14} className="text-red-500" />
) : null} ) : null}
<span>KW {getWeekNumber(currentWeekStart).toString().padStart(2, "0")}</span> <span>KW {getWeekNumber((() => { const days = getVisibleDays(); const mid = days[Math.floor(days.length / 2)] || currentWeekStart; return mid; })()).toString().padStart(2, "0")}</span>
<span className="text-gray-400">|</span>
<span>{(() => { const days = getVisibleDays(); const mid = days[Math.floor(days.length / 2)] || currentWeekStart; return mid; })().getFullYear()}</span>
</div> </div>
{/* Right: Settings + Overflow */} {/* Right: Settings + Overflow */}
@ -3920,7 +3951,7 @@ export default function WeeklyView() {
color: adjustColorForDarkMode(profile.cwColor || "#333333", darkMode), color: adjustColorForDarkMode(profile.cwColor || "#333333", darkMode),
filter: "brightness(var(--weekly-header-brightness, 1))" 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")}
</span> </span>
<span className="text-gray-400">|</span> <span className="text-gray-400">|</span>
<span <span
@ -6354,6 +6385,16 @@ function TaskItem({
</svg> </svg>
</span> </span>
)} )}
<span
style={{
whiteSpace: "pre-wrap",
wordBreak: "break-word",
overflow: "visible",
flex: 1,
}}
>
{task.title}
</span>
{task.subTasks && task.subTasks.length > 0 && !isSubTask && ( {task.subTasks && task.subTasks.length > 0 && !isSubTask && (
<button <button
onClick={(e) => { onClick={(e) => {
@ -6378,15 +6419,6 @@ function TaskItem({
</svg> </svg>
</span> </span>
)} )}
<span
style={{
whiteSpace: "pre-wrap",
wordBreak: "break-word",
overflow: "visible"
}}
>
{task.title}
</span>
</span> </span>
<div className="task-actions z-20"> <div className="task-actions z-20">