feat: UI improvements A-F — cookies, KW fix, mobile header, indicators
This commit is contained in:
parent
9be39b0b30
commit
b3503d9e4d
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user