refactor: Phase 2 — consolidate profile state reads

Replace all individual useState reads (viewStyle, timeFormat, language,
cellDuration, showTimeGrid, showNextTask, showSomeday, showAllDay, etc.)
with profile.fieldName reads. Individual useState declarations remain; only
read sites are migrated. No UI logic changed.

- getEffective() calls use profile.fieldName as global fallback
- workingHoursStart/End derived from profile.startHour/endHour
- All JSX reads (className, conditionals, child props) use profile.*
- SettingsSidebar value props use profile.* (setters unchanged)
- Add weekStartDay: 1 to profile defaults
- ProjectsSidebar function body reverted (uses language prop, not profile)
- darkMode excluded: localStorage-only, not an API profile field

v1.80.1
This commit is contained in:
mARTin 2026-03-30 22:51:40 +02:00
parent a5f6edd82d
commit bfb749a8a6
2 changed files with 182 additions and 182 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.80.0",
"version": "1.80.1",
"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": {

View File

@ -849,6 +849,7 @@ export default function WeeklyView() {
dayHeaderGap: "0.75em",
dateVerticalAlign: "middle",
allDayPosition: "above",
weekStartDay: 1,
focusTimerDuration: 25,
focusBreakDuration: 5,
headlineFont: "Oswald",
@ -951,18 +952,18 @@ export default function WeeklyView() {
viewSettingsRef.current = viewSettings;
const getEffective = <K extends keyof PerViewOverrides>(key: K, globalVal: PerViewOverrides[K]): PerViewOverrides[K] => {
const vs = viewSettingsRef.current[viewStyle];
const vs = viewSettingsRef.current[profile.viewStyle];
if (vs && vs[key] !== undefined) return vs[key] as PerViewOverrides[K];
return globalVal;
};
const isPerView = (key: keyof PerViewOverrides): boolean => {
const vs = viewSettingsRef.current[viewStyle];
const vs = viewSettingsRef.current[profile.viewStyle];
return !!(vs && vs[key] !== undefined);
};
const saveViewSetting = async <K extends keyof PerViewOverrides>(key: K, value: PerViewOverrides[K], perView: boolean) => {
const updated = { ...viewSettingsRef.current };
if (perView) {
updated[viewStyle] = { ...(updated[viewStyle] || {}), [key]: value };
updated[profile.viewStyle] = { ...(updated[profile.viewStyle] || {}), [key]: value };
} else {
// Remove per-view overrides for this key from ALL views and set globally
for (const v of Object.keys(updated)) {
@ -987,9 +988,9 @@ export default function WeeklyView() {
if (isPerView(key)) {
// Remove per-view override (revert to global)
const updated = { ...viewSettingsRef.current };
if (updated[viewStyle]) {
const { [key]: _, ...rest } = updated[viewStyle] as any;
updated[viewStyle] = rest;
if (updated[profile.viewStyle]) {
const { [key]: _, ...rest } = updated[profile.viewStyle] as any;
updated[profile.viewStyle] = rest;
}
viewSettingsRef.current = updated;
setViewSettings(updated);
@ -1011,17 +1012,17 @@ export default function WeeklyView() {
const [showAllDay, setShowAllDay] = useState(true);
// Effective per-view values (override if set for current view, else global)
const effectiveHourLabelFormat = getEffective("hourLabelFormat", hourLabelFormat);
const effectiveShowSubHourSlots = getEffective("showSubHourSlots", showSubHourSlots);
const effectiveHourLabelFormat = getEffective("hourLabelFormat", profile.hourLabelFormat ?? "short");
const effectiveShowSubHourSlots = getEffective("showSubHourSlots", profile.showSubHourSlots ?? true);
const effectiveWeatherEnabled = getEffective("weatherEnabled", profile.weatherEnabled);
const effectiveWeatherDisplay = (getEffective("weatherDisplay", WEATHER_DISPLAY_DEFAULTS) || WEATHER_DISPLAY_DEFAULTS) as WeatherDisplayKey[];
const effectiveShowTaskCheckboxes = getEffective("showTaskCheckboxes", profile.showTaskCheckboxes);
const effectiveShowProjectIcons = getEffective("showProjectIcons", profile.showProjectIcons);
const effectiveShowSomeday = getEffective("showSomeday", showSomeday);
const effectiveShowAllDay = getEffective("showAllDayEvents", showAllDay);
const effectiveAllDayPosition = getEffective("allDayPosition", allDayPosition) || "above";
const effectiveShowSomeday = getEffective("showSomeday", profile.showSomeday ?? true);
const effectiveShowAllDay = getEffective("showAllDayEvents", profile.showAllDayEvents ?? true);
const effectiveAllDayPosition = getEffective("allDayPosition", profile.allDayPosition ?? "above") || "above";
const effectiveShowCompletedTasks = getEffective("showCompletedTasks", profile.showCompletedTasks !== false);
const effectiveCellDuration = getEffective("cellDuration", cellDuration) as CellDuration;
const effectiveCellDuration = getEffective("cellDuration", profile.cellDuration ?? 30) as CellDuration;
const effectiveStartHour = getEffective("startHour", profile.startHour ?? 8) ?? 8;
const effectiveEndHour = getEffective("endHour", profile.endHour ?? 18) ?? 18;
@ -1321,14 +1322,14 @@ export default function WeeklyView() {
useEffect(() => {
if (!mounted) return;
localStorage.setItem("weekly-week-start", String(weekStartDay));
localStorage.setItem("weekly-week-start", String(profile.weekStartDay ?? 1));
// REMOVED: Re-align current week start when start day changes
// This was forcing the view to snap to Monday, breaking the "Yesterday as first column" setting.
// setCurrentWeekStart(prev => getStartOfWeek(prev, weekStartDay));
}, [weekStartDay, mounted]);
}, [profile.weekStartDay, mounted]);
// Translation helper
const t = translations[language] || translations["en"];
const t = translations[profile.language] || translations["en"];
// Refs for scroll
const dayColumnsRef = useRef<HTMLDivElement[]>([]);
@ -1371,7 +1372,7 @@ export default function WeeklyView() {
window.removeEventListener('resize', updateHeight);
if (resizeObserver) resizeObserver.disconnect();
};
}, [dayHeaderRef.current, cellDuration, viewDays, isMobile, profile.mobileDateLayout, profile.dateLayout, profile.dateAlignment, profile.dayHeaderGap, profile.headlineFontSize, profile.headlineFontWeight, profile.dateFontSize, profile.dateVerticalAlign, profile.headerDisplay, profile.weekdayFormat, viewStyle]);
}, [dayHeaderRef.current, profile.cellDuration, viewDays, isMobile, profile.mobileDateLayout, profile.dateLayout, profile.dateAlignment, profile.dayHeaderGap, profile.headlineFontSize, profile.headlineFontWeight, profile.dateFontSize, profile.dateVerticalAlign, profile.headerDisplay, profile.weekdayFormat, profile.viewStyle]);
const somedaySectionRef = useRef<HTMLElement | null>(null);
// Unified scroll sync handlers
@ -1383,8 +1384,8 @@ export default function WeeklyView() {
};
const jumpToHour = (hour: number) => {
const slotsPerHour = 60 / cellDuration;
const slotHeight = getSlotHeight(cellDuration);
const slotsPerHour = 60 / effectiveCellDuration;
const slotHeight = getSlotHeight(effectiveCellDuration);
const scrollOffset = hour * slotsPerHour * slotHeight;
console.log(`[SCROLL] Jumping to hour ${hour} (offset ${scrollOffset}px)`);
@ -1449,8 +1450,8 @@ export default function WeeklyView() {
};
// Working hours range (configurable)
const workingHoursStart = startHour;
const workingHoursEnd = endHour;
const workingHoursStart = profile.startHour ?? 8;
const workingHoursEnd = profile.endHour ?? 18;
// Fetch calendar events
// Find connectionId for a given calendarId
@ -1984,7 +1985,7 @@ export default function WeeklyView() {
// Mobile: show a sticky day bar by reading scroll position on the actual grid scroll container
useEffect(() => {
if (!isMobile || !showTimeGrid) return;
if (!isMobile || !profile.showTimeGrid) return;
const grid = gridRef.current;
if (!grid) return;
@ -2009,7 +2010,7 @@ export default function WeeklyView() {
const dateStr = (currentCol as Element).getAttribute('data-date');
if (dateStr) {
const d = new Date(dateStr + 'T00:00:00');
const dayNames = language === 'de'
const dayNames = profile.language === 'de'
? ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const label = `${dayNames[d.getDay()]} ${d.getDate()}.${d.getMonth() + 1}.`;
@ -2021,7 +2022,7 @@ export default function WeeklyView() {
grid.addEventListener('scroll', updateStickyDay, { passive: true });
updateStickyDay();
return () => grid.removeEventListener('scroll', updateStickyDay);
}, [isMobile, showTimeGrid, currentWeekStart, viewDays, language]);
}, [isMobile, profile.showTimeGrid, currentWeekStart, viewDays, profile.language]);
// Compute the goal date key: for "week" scope, normalize to Monday of that week; for "day", use the exact date
const getGoalDateKey = useCallback(
@ -2212,7 +2213,7 @@ export default function WeeklyView() {
setStartHour(data.user.startHour);
if (data.user.endHour !== undefined)
setEndHour(data.user.endHour);
if (data.user.viewStyle !== undefined) {
if (data.user.profile.viewStyle !== undefined) {
setViewStyle(data.user.viewStyle as ViewStyle);
setShowTimeGrid(data.user.showTimeGrid ?? true);
}
@ -5029,7 +5030,7 @@ export default function WeeklyView() {
24,
);
const fontSizeScale = fontSize === "S" ? 0.85 : fontSize === "L" ? 1.15 : 1;
const fontSizeScale = profile.fontSize === "S" ? 0.85 : profile.fontSize === "L" ? 1.15 : 1;
const mobileScale = isMobile ? (profile.mobileFontScale || 1.0) : 1.0;
const fontVal = (v: string | undefined) => v && v !== "__custom__" ? v : "";
const scaleRem = (base: string) => {
@ -5049,8 +5050,8 @@ export default function WeeklyView() {
"--weekly-item-hover": activeTheme.color0,
} : {}),
"--weekly-font-headline":
fontVal(profile.headlineFont) || headlineFont
? `"${fontVal(profile.headlineFont) || headlineFont}", sans-serif`
fontVal(profile.headlineFont)
? `"${fontVal(profile.headlineFont)}", sans-serif`
: "var(--font-headline)",
"--weekly-headline-size": scaleRem(profile.headlineFontSize || "1.25rem"),
"--weekly-headline-weight": profile.headlineFontWeight || "900",
@ -5088,13 +5089,12 @@ export default function WeeklyView() {
"--weekly-task-size": scaleRem(profile.taskFontSize || "0.9rem"),
"--weekly-task-weight": profile.taskFontWeight || "400",
"--weekly-event-font":
fontVal(profile.eventFontFamily) || eventFontFamily
? `"${fontVal(profile.eventFontFamily) || eventFontFamily}", sans-serif`
fontVal(profile.eventFontFamily)
? `"${fontVal(profile.eventFontFamily)}", sans-serif`
: "var(--weekly-font)",
"--weekly-event-size": scaleRem(profile.eventFontSize || eventFontSize || "0.85rem"),
"--weekly-event-weight":
profile.eventFontWeight || eventFontWeight || "400",
"--font-weight-body": profile.fontWeight || fontWeight || "400",
"--weekly-event-size": scaleRem(profile.eventFontSize || "0.85rem"),
"--weekly-event-weight": profile.eventFontWeight || "400",
"--font-weight-body": profile.fontWeight || "400",
"--weekly-weekend-sat": activeTheme?.color3 || (darkMode
? invertColor(profile.weekendColorSat || "#666666")
: profile.weekendColorSat || "#666666"),
@ -5125,7 +5125,7 @@ export default function WeeklyView() {
style={{ alignItems: "center", justifyContent: "center" }}
>
<div style={{ color: "var(--weekly-text-light)" }}>
{translations[language]?.loading || translations["en"].loading}
{translations[profile.language]?.loading || translations["en"].loading}
</div>
</div>
);
@ -5172,7 +5172,7 @@ export default function WeeklyView() {
style={isAllDayExpanded && allDayHeight ? { height: `${allDayHeight}px`, overflowY: 'auto' } : undefined}
>
<div style={{ display: "flex", flexDirection: "row" }}>
{showTimeGrid && (
{profile.showTimeGrid && (
<div
className="all-day-label-column"
onClick={() => setIsAllDayExpanded(!isAllDayExpanded)}
@ -5293,7 +5293,7 @@ export default function WeeklyView() {
return (
<div
className={`weekly-container ${darkMode ? "dark-mode" : ""} font-size-${fontSize.toLowerCase()} ${viewStyle}-view ${showTimeGrid ? "time-grid-on" : "time-grid-off"}`}
className={`weekly-container ${darkMode ? "dark-mode" : ""} font-size-${(profile.fontSize ?? "M").toLowerCase()} ${profile.viewStyle}-view ${profile.showTimeGrid ? "time-grid-on" : "time-grid-off"}`}
style={containerStyle}
>
{/* Mobile sticky day indicator */}
@ -5349,30 +5349,30 @@ export default function WeeklyView() {
}}
>
<div style={{ fontSize: "0.85rem", fontWeight: 700, color: darkMode ? "#e5e7eb" : "#333", marginBottom: "2px" }}>
{language === "de" ? "Einstellungen" : "Preferences"}
{profile.language === "de" ? "Einstellungen" : "Preferences"}
</div>
{/* Navigation Row */}
<div style={{ display: "flex", justifyContent: "center", gap: "4px", padding: "4px 0", borderBottom: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingBottom: "10px" }}>
<button onClick={() => { goToPrevWeek(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Previous Week"><ChevronsLeft size={16} /></button>
<button onClick={() => { goToPrevDay(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Previous Day"><ChevronLeft size={16} /></button>
<button onClick={() => { goToToday(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "56px", fontWeight: 700, fontSize: "0.7rem" }} title="Today">{language === "de" ? "Heute" : "Today"}</button>
<button onClick={() => { goToToday(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "56px", fontWeight: 700, fontSize: "0.7rem" }} title="Today">{profile.language === "de" ? "Heute" : "Today"}</button>
<button onClick={() => { goToNextDay(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Next Day"><ChevronRight size={16} /></button>
<button onClick={() => { goToNextWeek(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Next Week"><ChevronsRight size={16} /></button>
</div>
{/* Quick Actions */}
<div style={{ display: "flex", flexDirection: "column", gap: "1px" }}>
<button onClick={() => { setIsSearchOpen(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Search size={16} /> <span>{language === "de" ? "Suche" : "Search"}</span></button>
<button onClick={() => { setShowDatePicker(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Calendar size={16} /> <span>{language === "de" ? "Datum wählen" : "Jump to date"}</span></button>
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Plus size={16} /> <span>{language === "de" ? "Kalendereintrag" : "Calendar Event"}</span></button>
<button onClick={() => { setShowProjectsSidebar(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><FolderPlus size={16} /> <span>{language === "de" ? "Projekte" : "Projects"}</span></button>
<button onClick={() => { setIsRecurringTasksOpen(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Repeat size={16} /> <span>{language === "de" ? "Wiederkehrend" : "Recurring Tasks"}</span></button>
<button onClick={() => { const nv = !showNextTask; setShowNextTask(nv); saveSetting("showNextTask", nv); }} style={qsActionStyle(darkMode)}>
{showNextTask ? <Play size={16} /> : <Target size={16} />}
<span>{showNextTask ? (language === "de" ? "Nächste Aufgabe" : "Next Task") : (language === "de" ? "Ziel" : "Goal")}</span>
<button onClick={() => { setIsSearchOpen(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Search size={16} /> <span>{profile.language === "de" ? "Suche" : "Search"}</span></button>
<button onClick={() => { setShowDatePicker(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Calendar size={16} /> <span>{profile.language === "de" ? "Datum wählen" : "Jump to date"}</span></button>
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Plus size={16} /> <span>{profile.language === "de" ? "Kalendereintrag" : "Calendar Event"}</span></button>
<button onClick={() => { setShowProjectsSidebar(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><FolderPlus size={16} /> <span>{profile.language === "de" ? "Projekte" : "Projects"}</span></button>
<button onClick={() => { setIsRecurringTasksOpen(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Repeat size={16} /> <span>{profile.language === "de" ? "Wiederkehrend" : "Recurring Tasks"}</span></button>
<button onClick={() => { const nv = !profile.showNextTask; setShowNextTask(nv); saveSetting("showNextTask", nv); }} style={qsActionStyle(darkMode)}>
{profile.showNextTask ? <Play size={16} /> : <Target size={16} />}
<span>{profile.showNextTask ? (profile.language === "de" ? "Nächste Aufgabe" : "Next Task") : (profile.language === "de" ? "Ziel" : "Goal")}</span>
</button>
<button onClick={() => { setShowFocusMode(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Zap size={16} /> <span>{language === "de" ? "Fokus" : "Focus"}</span></button>
<button onClick={() => { setShowFocusMode(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Zap size={16} /> <span>{profile.language === "de" ? "Fokus" : "Focus"}</span></button>
</div>
{/* Separator */}
@ -5381,7 +5381,7 @@ export default function WeeklyView() {
{/* View Style */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>
{language === "de" ? "Ansicht" : "View"}
{profile.language === "de" ? "Ansicht" : "View"}
</span>
<div style={{ display: "flex", gap: "4px" }}>
{[
@ -5391,7 +5391,7 @@ export default function WeeklyView() {
{ key: "kanban", icon: <Kanban size={13} /> },
].map((v) => (
<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); }}
style={{ flex: 1, padding: "5px 0", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: viewStyle === v.key ? 700 : 400, background: viewStyle === v.key ? "#0ea5e9" : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewStyle === v.key ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
style={{ flex: 1, padding: "5px 0", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: profile.viewStyle ===v.key ? 700 : 400, background: profile.viewStyle ===v.key ? "#0ea5e9" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle ===v.key ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
{v.icon}
</button>
))}
@ -5401,7 +5401,7 @@ export default function WeeklyView() {
{/* Columns / Days */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>
{language === "de" ? "Spalten" : "Days"}
{profile.language === "de" ? "Spalten" : "Days"}
</span>
<div style={{ display: "flex", gap: "4px" }}>
{[1, 2, 3, 5, 7].map((num) => (
@ -5414,10 +5414,10 @@ export default function WeeklyView() {
</div>
{/* Slot Duration (only with time grid) */}
{showTimeGrid && (
{profile.showTimeGrid && (
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>
{language === "de" ? "Zeitfenster" : "Slot"}
{profile.language === "de" ? "Zeitfenster" : "Slot"}
</span>
<div style={{ display: "flex", gap: "4px" }}>
{[15, 30, 60].map((d) => (
@ -5433,12 +5433,12 @@ export default function WeeklyView() {
{/* Text size */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>
{language === "de" ? "Textgröße" : "Text size"}
{profile.language === "de" ? "Textgröße" : "Text size"}
</span>
<div style={{ display: "flex", gap: "4px" }}>
{(["S", "M", "L"] as const).map((size) => (
<button key={size} onClick={() => { setFontSize(size); saveSetting("fontSize", size); }}
style={{ padding: "4px 10px", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: fontSize === size ? 700 : 400, background: fontSize === size ? (darkMode ? "#374151" : "#333") : (darkMode ? "#1f2937" : "#e5e7eb"), color: fontSize === size ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
style={{ padding: "4px 10px", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: profile.fontSize === size ? 700 : 400, background: profile.fontSize === size ? (darkMode ? "#374151" : "#333") : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.fontSize === size ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
{size}
</button>
))}
@ -5450,31 +5450,31 @@ export default function WeeklyView() {
{/* Toggle switches */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Irgendwann" : "Someday"}</span>
<button onClick={() => { const v = !showSomeday; setShowSomeday(v); saveSetting("showSomeday", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Irgendwann" : "Someday"}</span>
<button onClick={() => { const v = !profile.showSomeday; setShowSomeday(v); saveSetting("showSomeday", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowSomeday ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Zeitplan" : "Schedule"}</span>
<button onClick={() => { const v = !showTimeGrid; setShowTimeGrid(v); saveSetting("showTimeGrid", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{showTimeGrid ? <Eye size={16} /> : <EyeOff size={16} />}
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Zeitplan" : "Schedule"}</span>
<button onClick={() => { const v = !profile.showTimeGrid; setShowTimeGrid(v); saveSetting("showTimeGrid", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{profile.showTimeGrid ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Ganztägig" : "All-day"}</span>
<button onClick={() => { const v = !showAllDay; setShowAllDay(v); saveSetting("showAllDayEvents", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Ganztägig" : "All-day"}</span>
<button onClick={() => { const v = !profile.showAllDayEvents; setShowAllDay(v); saveSetting("showAllDayEvents", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowAllDay ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Checkboxen" : "Checkboxes"}</span>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Checkboxen" : "Checkboxes"}</span>
<button onClick={() => { const v = !profile.showTaskCheckboxes; setProfile({ ...profile, showTaskCheckboxes: v }); saveSetting("showTaskCheckboxes", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowTaskCheckboxes ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Projekt-Icons" : "Project Icons"}</span>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Projekt-Icons" : "Project Icons"}</span>
<button onClick={() => { const v = !profile.showProjectIcons; setProfile({ ...profile, showProjectIcons: v }); saveSetting("showProjectIcons", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowProjectIcons ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
@ -5482,30 +5482,30 @@ export default function WeeklyView() {
{/* Start on */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Starten mit" : "Start on"}</span>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Starten mit" : "Start on"}</span>
<div style={{ display: "flex", gap: "4px" }}>
<button onClick={() => { setProfile({ ...profile, startDayOffset: 0 }); saveSetting("startDayOffset", 0); const d = new Date(); d.setHours(0, 0, 0, 0); setCurrentWeekStart(d); }}
style={{ padding: "4px 10px", fontSize: "0.75rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: (profile.startDayOffset || 0) === 0 ? 700 : 400, background: (profile.startDayOffset || 0) === 0 ? (darkMode ? "#374151" : "#333") : (darkMode ? "#1f2937" : "#e5e7eb"), color: (profile.startDayOffset || 0) === 0 ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
{language === "de" ? "Heute" : "Today"}
{profile.language === "de" ? "Heute" : "Today"}
</button>
<button onClick={() => { setProfile({ ...profile, startDayOffset: -1 }); saveSetting("startDayOffset", -1); const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - 1); setCurrentWeekStart(d); }}
style={{ padding: "4px 10px", fontSize: "0.75rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: profile.startDayOffset === -1 ? 700 : 400, background: profile.startDayOffset === -1 ? (darkMode ? "#374151" : "#333") : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.startDayOffset === -1 ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
{language === "de" ? "Gestern" : "Yesterday"}
{profile.language === "de" ? "Gestern" : "Yesterday"}
</button>
</div>
</div>
{/* Display mode */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{language === "de" ? "Anzeige" : "Display"}</span>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Anzeige" : "Display"}</span>
<div style={{ display: "flex", gap: "4px" }}>
<button onClick={() => setDarkMode(false)}
style={{ padding: "4px 10px", fontSize: "0.75rem", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", gap: "4px", fontWeight: !darkMode ? 700 : 400, background: !darkMode ? "#333" : (darkMode ? "#1f2937" : "#e5e7eb"), color: !darkMode ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}>
<Sun size={12} /> {language === "de" ? "Hell" : "Light"}
<Sun size={12} /> {profile.language === "de" ? "Hell" : "Light"}
</button>
<button onClick={() => setDarkMode(true)}
style={{ padding: "4px 10px", fontSize: "0.75rem", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", gap: "4px", fontWeight: darkMode ? 700 : 400, background: darkMode ? "#374151" : "#e5e7eb", color: darkMode ? "#fff" : "#6b7280" }}>
<Moon size={12} /> {language === "de" ? "Dunkel" : "Dark"}
<Moon size={12} /> {profile.language === "de" ? "Dunkel" : "Dark"}
</button>
</div>
</div>
@ -5529,7 +5529,7 @@ export default function WeeklyView() {
style={{ display: "flex", alignItems: "center", gap: "6px", background: "none", border: "none", cursor: "pointer", fontSize: "0.75rem", color: darkMode ? "#6b7280" : "#9ca3af", padding: "4px 0" }}
>
<PanelLeftClose size={16} />
{language === "de" ? "Ausblenden" : "Hide"}
{profile.language === "de" ? "Ausblenden" : "Hide"}
</button>
</div>
</>
@ -5539,7 +5539,7 @@ export default function WeeklyView() {
{showProjectsSidebar && (
<ProjectsSidebar
darkMode={darkMode}
language={language}
language={profile.language}
projects={projects}
onProjectsChanged={fetchProjects}
onClose={() => setShowProjectsSidebar(false)}
@ -5608,14 +5608,14 @@ export default function WeeklyView() {
d.getFullYear() === today.getFullYear()
);
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
return refDate.toLocaleDateString(profile.language, { day: '2-digit', month: '2-digit', year: 'numeric' });
})() :
profile.headerDisplay === "month_year" ?
getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' }) :
getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: 'long', year: 'numeric' }) :
profile.headerDisplay === "custom" ?
formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t) :
formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), profile.language, t) :
profile.headerDisplay === "month" ?
getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long" }) :
getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: "long" }) :
`KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")} | ${getCWReferenceDate(getVisibleDays()).getFullYear()}`
}
</span>
@ -5638,7 +5638,7 @@ export default function WeeklyView() {
<button
onClick={() => setShowQuickSettings(true)}
className="p-1.5 rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:hover:bg-gray-700 transition-colors"
title={language === "de" ? "Schnelleinstellungen" : "Quick Settings"}
title={profile.language === "de" ? "Schnelleinstellungen" : "Quick Settings"}
>
<PanelLeftOpen size={16} />
</button>
@ -5647,28 +5647,28 @@ export default function WeeklyView() {
<div className="flex items-center gap-0.5 bg-gray-100 dark:bg-gray-800 rounded p-0.5" title={t.viewStyle}>
<button
onClick={() => { setViewStyle("simple"); setShowTimeGrid(true); saveSetting("viewStyle", "simple"); saveSetting("showTimeGrid", true); }}
className={`p-1.5 rounded transition-colors ${viewStyle === "simple" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
className={`p-1.5 rounded transition-colors ${profile.viewStyle ==="simple" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
title={t.simpleView}
>
<Calendar size={16} />
</button>
<button
onClick={() => { setViewStyle("calendar"); setShowTimeGrid(true); saveSetting("viewStyle", "calendar"); saveSetting("showTimeGrid", true); }}
className={`p-1.5 rounded transition-colors ${viewStyle === "calendar" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
className={`p-1.5 rounded transition-colors ${profile.viewStyle ==="calendar" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
title={t.calendarView}
>
<CalendarDays size={16} />
</button>
<button
onClick={() => { setViewStyle("list"); setShowTimeGrid(false); saveSetting("viewStyle", "list"); saveSetting("showTimeGrid", false); }}
className={`p-1.5 rounded transition-colors ${viewStyle === "list" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
className={`p-1.5 rounded transition-colors ${profile.viewStyle ==="list" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
title={t.listView}
>
<ListTodo size={16} />
</button>
<button
onClick={() => { setViewStyle("kanban"); saveSetting("viewStyle", "kanban"); }}
className={`p-1.5 rounded transition-colors ${viewStyle === "kanban" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
className={`p-1.5 rounded transition-colors ${profile.viewStyle ==="kanban" ? "bg-white shadow-sm text-black dark:bg-gray-700 dark:text-white" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
title={t.kanbanView}
>
<Kanban size={16} />
@ -5697,7 +5697,7 @@ export default function WeeklyView() {
</div>
{/* Slot Duration — desktop only (available in sidebar on tablet) */}
{showTimeGrid && (
{profile.showTimeGrid && (
<div
className="header-desktop-only items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1"
title="Slot Duration"
@ -5739,7 +5739,7 @@ export default function WeeklyView() {
setShowDatePicker(false);
}}
onClose={() => setShowDatePicker(false)}
language={language}
language={profile.language}
anchorRef={datePickerBtnRef}
/>
)}
@ -5754,9 +5754,9 @@ export default function WeeklyView() {
profile.headerDisplay === "none"
? ""
: profile.headerDisplay === "month"
? getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long" })
? getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: "long" })
: profile.headerDisplay === "month_year"
? getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long", year: "numeric" })
? getCWReferenceDate(getVisibleDays()).toLocaleDateString(profile.language, { month: "long", year: "numeric" })
: profile.headerDisplay === "date"
? (() => {
const today = new Date();
@ -5766,10 +5766,10 @@ export default function WeeklyView() {
d.getFullYear() === today.getFullYear()
);
const refDate = isTodayInWeek ? today : getCWReferenceDate(getVisibleDays());
return refDate.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' });
return refDate.toLocaleDateString(profile.language, { day: '2-digit', month: '2-digit', year: 'numeric' });
})()
: profile.headerDisplay === "custom"
? formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)
? formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), profile.language, t)
: `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}`
}
</span>
@ -5833,9 +5833,9 @@ export default function WeeklyView() {
/>
) : (
<span
onClick={() => !showNextTask && setIsEditingGoal(true)}
className={`cursor-pointer font-medium italic transition-colors ${showNextTask ? "cursor-default text-gray-600 dark:text-white hover:text-black dark:hover:text-gray-100" : "text-gray-600 dark:text-yellow-400 hover:text-black dark:hover:text-yellow-300"}`}
title={showNextTask ? "Next task" : "Edit goal"}
onClick={() => !profile.showNextTask && setIsEditingGoal(true)}
className={`cursor-pointer font-medium italic transition-colors ${profile.showNextTask ? "cursor-default text-gray-600 dark:text-white hover:text-black dark:hover:text-gray-100" : "text-gray-600 dark:text-yellow-400 hover:text-black dark:hover:text-yellow-300"}`}
title={profile.showNextTask ? "Next task" : "Edit goal"}
style={{
fontFamily: profile.goalFontFamily
? `"${profile.goalFontFamily}", sans-serif`
@ -5852,7 +5852,7 @@ export default function WeeklyView() {
WebkitBoxOrient: "vertical" as const,
}}
>
{showNextTask
{profile.showNextTask
? (() => {
const today = new Date();
today.setHours(0, 0, 0, 0);
@ -5900,8 +5900,8 @@ export default function WeeklyView() {
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); }} className="weekly-btn-icon" title="Add Calendar Event">
<Plus size={17} className="text-gray-600 hover:text-black transition-colors" />
</button>
<button onClick={() => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }} className={`weekly-btn-icon ${showNextTask ? "active" : ""}`} title={showNextTask ? "Showing Next Task" : "Showing Goal"}>
{showNextTask ? <Play size={17} className="text-teal-600" /> : <Target size={17} className="text-gray-400" />}
<button onClick={() => { const newVal = !profile.showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); }} className={`weekly-btn-icon ${profile.showNextTask ? "active" : ""}`} title={profile.showNextTask ? "Showing Next Task" : "Showing Goal"}>
{profile.showNextTask ? <Play size={17} className="text-teal-600" /> : <Target size={17} className="text-gray-400" />}
</button>
<button onClick={() => setShowFocusMode(true)} className="weekly-btn-icon" title="Enter Focus Mode">
<Zap size={17} className="text-gray-600 hover:text-yellow-500 transition-colors" />
@ -5935,15 +5935,15 @@ export default function WeeklyView() {
<div className="header-overflow-menu">
<button onClick={() => { handleUndo(); setShowHeaderMore(false); }} disabled={undoCount === 0}><Undo2 size={16} /> <span>Undo</span></button>
<button onClick={() => { handleRedo(); setShowHeaderMore(false); }} disabled={redoCount === 0}><Redo2 size={16} /> <span>Redo</span></button>
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); setShowHeaderMore(false); }}><Plus size={16} /> <span>{language === "de" ? "Termin erstellen" : "Add Event"}</span></button>
<button onClick={() => { setIsRecurringTasksOpen(true); setShowHeaderMore(false); }}><Repeat size={16} /> <span>{language === "de" ? "Wiederkehrend" : "Recurring"}</span></button>
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); setShowHeaderMore(false); }}><Plus size={16} /> <span>{profile.language === "de" ? "Termin erstellen" : "Add Event"}</span></button>
<button onClick={() => { setIsRecurringTasksOpen(true); setShowHeaderMore(false); }}><Repeat size={16} /> <span>{profile.language === "de" ? "Wiederkehrend" : "Recurring"}</span></button>
<button onClick={() => { const nv = !showNextTask; setShowNextTask(nv); saveSetting("showNextTask", nv); setShowHeaderMore(false); }}>{showNextTask ? <Play size={16} /> : <Target size={16} />} <span>{showNextTask ? "Next Task" : "Goal"}</span></button>
<button onClick={() => { setShowFocusMode(true); setShowHeaderMore(false); }}><Zap size={16} /> <span>{language === "de" ? "Fokus" : "Focus"}</span></button>
<button onClick={() => { setShowFocusMode(true); setShowHeaderMore(false); }}><Zap size={16} /> <span>{profile.language === "de" ? "Fokus" : "Focus"}</span></button>
<button onClick={() => { setDarkMode(!darkMode); setShowHeaderMore(false); }}>{darkMode ? <Sun size={16} /> : <Moon size={16} />} <span>{darkMode ? "Light" : "Dark"}</span></button>
<button onClick={() => { setShowProjectsSidebar(true); setShowHeaderMore(false); }}><FolderPlus size={16} /> <span>{language === "de" ? "Neues Projekt" : "New Project"}</span></button>
<button onClick={() => { setShowProjectsSidebar(true); setShowHeaderMore(false); }}><FolderPlus size={16} /> <span>{profile.language === "de" ? "Neues Projekt" : "New Project"}</span></button>
<div style={{ borderTop: "1px solid var(--border-color, #e5e7eb)", margin: "2px 0" }} />
<div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }}>
<span style={{ fontSize: "12px", color: "#888", marginRight: "4px" }}>{language === "de" ? "Tage" : "Days"}:</span>
<span style={{ fontSize: "12px", color: "#888", marginRight: "4px" }}>{profile.language === "de" ? "Tage" : "Days"}:</span>
{[1, 2, 3, 5, 7].map((num) => (
<button
key={num}
@ -5954,9 +5954,9 @@ export default function WeeklyView() {
</button>
))}
</div>
{showTimeGrid && (
{profile.showTimeGrid && (
<div style={{ padding: "6px 12px", display: "flex", alignItems: "center", gap: "6px" }}>
<span style={{ fontSize: "12px", color: "#888", marginRight: "4px" }}>{language === "de" ? "Slot" : "Slot"}:</span>
<span style={{ fontSize: "12px", color: "#888", marginRight: "4px" }}>{profile.language === "de" ? "Slot" : "Slot"}:</span>
{[15, 30, 60].map((duration) => (
<button
key={duration}
@ -5988,10 +5988,10 @@ export default function WeeklyView() {
</header>
{/* All-Day Events Section (above position) — hidden in kanban */}
{viewStyle !== "kanban" && effectiveAllDayPosition === "above" && allDaySection}
{profile.viewStyle !== "kanban" && effectiveAllDayPosition === "above" && allDaySection}
{/* Kanban Board View */}
{viewStyle === "kanban" && (() => {
{profile.viewStyle ==="kanban" && (() => {
// Combine all tasks (weekly + someday) for kanban
const allSomedayTasks = somedayLists.flatMap(l => l.tasks);
const allKanbanTasks = [...tasks, ...allSomedayTasks];
@ -6007,7 +6007,7 @@ export default function WeeklyView() {
if (!weeks.has(key)) {
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekEnd.getDate() + 6);
weeks.set(key, `${weekStart.toLocaleDateString(language, { month: "short", day: "numeric" })} - ${weekEnd.toLocaleDateString(language, { month: "short", day: "numeric" })}`);
weeks.set(key, `${weekStart.toLocaleDateString(profile.language, { month: "short", day: "numeric" })} - ${weekEnd.toLocaleDateString(profile.language, { month: "short", day: "numeric" })}`);
}
}
});
@ -6125,12 +6125,12 @@ export default function WeeklyView() {
)}
{task.markdownContent && (
<div className="kanban-card-has-notes" style={{ fontSize: "0.65rem", color: "#9ca3af", marginTop: "2px", display: "flex", alignItems: "center", gap: "3px" }}>
<FileText size={10} /> {language === "de" ? "Notizen" : "Notes"}
<FileText size={10} /> {profile.language === "de" ? "Notizen" : "Notes"}
</div>
)}
{(task.scheduledDate || task.startTime) && (
<div className="kanban-card-date">
{task.scheduledDate && new Date(task.scheduledDate).toLocaleDateString(language, { weekday: "short", month: "short", day: "numeric" })}
{task.scheduledDate && new Date(task.scheduledDate).toLocaleDateString(profile.language, { weekday: "short", month: "short", day: "numeric" })}
{task.startTime && <span className="kanban-card-time"> {task.startTime}</span>}
</div>
)}
@ -6213,7 +6213,7 @@ export default function WeeklyView() {
{projects.length > 0 && (
<div className="kanban-project-chip" style={{ background: darkMode ? "#1f2937" : "#f3f4f6", color: "#9ca3af" }} {...kanbanProjectDrop(null)}>
<X size={12} />
<span>{language === "de" ? "Kein Projekt" : "No project"}</span>
<span>{profile.language === "de" ? "Kein Projekt" : "No project"}</span>
</div>
)}
{/* Spacer pushes filters to right */}
@ -6286,7 +6286,7 @@ export default function WeeklyView() {
const updated = kanbanStages.map((s, i) => i === idx ? { ...s, color: e.target.value } : s);
saveKanbanStages(updated);
}}
title={language === "de" ? "Farbe ändern" : "Change color"}
title={profile.language === "de" ? "Farbe ändern" : "Change color"}
style={{ width: "14px", height: "14px", border: "none", cursor: "pointer", padding: 0, borderRadius: "50%", flexShrink: 0 }}
/>
<span
@ -6319,12 +6319,12 @@ export default function WeeklyView() {
}}
style={{ padding: "2px 6px", fontSize: "0.65rem", borderRadius: "4px", border: "none", cursor: "pointer", background: "#ef4444", color: "#fff", fontWeight: 600, whiteSpace: "nowrap" }}
>
{language === "de" ? "Ja, löschen" : "Confirm"}
{profile.language === "de" ? "Ja, löschen" : "Confirm"}
</button>
<button
onClick={() => setKanbanDeleteStageId(null)}
style={{ padding: "2px", background: "none", border: "none", cursor: "pointer", color: "#9ca3af", display: "flex" }}
title={language === "de" ? "Abbrechen" : "Cancel"}
title={profile.language === "de" ? "Abbrechen" : "Cancel"}
>
<X size={12} />
</button>
@ -6333,7 +6333,7 @@ export default function WeeklyView() {
<button
onClick={() => setKanbanDeleteStageId(stage.id)}
className="kanban-column-delete"
title={language === "de" ? "Phase löschen" : "Delete stage"}
title={profile.language === "de" ? "Phase löschen" : "Delete stage"}
>
<Trash2 size={12} />
</button>
@ -6346,7 +6346,7 @@ export default function WeeklyView() {
<input
type="text"
autoFocus
placeholder={language === "de" ? "Aufgabe eingeben..." : "Enter task..."}
placeholder={profile.language === "de" ? "Aufgabe eingeben..." : "Enter task..."}
value={kanbanNewTaskTitle}
onChange={(e) => setKanbanNewTaskTitle(e.target.value)}
onKeyDown={(e) => {
@ -6401,7 +6401,7 @@ export default function WeeklyView() {
}}
>
<Plus size={12} />
{language === "de" ? "Aufgabe" : "Add task"}
{profile.language === "de" ? "Aufgabe" : "Add task"}
</button>
)}
</div>
@ -6438,7 +6438,7 @@ export default function WeeklyView() {
<input
type="text"
autoFocus
placeholder={language === "de" ? "Aufgabe eingeben..." : "Enter task..."}
placeholder={profile.language === "de" ? "Aufgabe eingeben..." : "Enter task..."}
value={kanbanNewTaskTitle}
onChange={(e) => setKanbanNewTaskTitle(e.target.value)}
onKeyDown={(e) => {
@ -6493,7 +6493,7 @@ export default function WeeklyView() {
}}
>
<Plus size={12} />
{language === "de" ? "Aufgabe" : "Add task"}
{profile.language === "de" ? "Aufgabe" : "Add task"}
</button>
)}
</div>
@ -6506,15 +6506,15 @@ export default function WeeklyView() {
})()}
{/* Main Grid with Time Column */}
{viewStyle !== "kanban" && <div style={{ position: "relative", flex: 1, minHeight: 0, overflow: 'hidden' }}>
<div className="time-grid-wrapper" ref={gridRef as React.Ref<HTMLDivElement>} onScroll={handleGridScroll} style={showTimeGrid ? {
{profile.viewStyle !== "kanban" && <div style={{ position: "relative", flex: 1, minHeight: 0, overflow: 'hidden' }}>
<div className="time-grid-wrapper" ref={gridRef as React.Ref<HTMLDivElement>} onScroll={handleGridScroll} style={profile.showTimeGrid ? {
height: '100%',
overflowY: 'auto',
overflowX: 'hidden',
WebkitOverflowScrolling: 'touch' as any,
} : undefined}>
{/* Time Column */}
{showTimeGrid && (
{profile.showTimeGrid && (
<div
className="time-column"
style={{
@ -6611,14 +6611,14 @@ export default function WeeklyView() {
title={isHourStart ? `Jump to ${hour}:00` : undefined}
>
{(isHourStart || effectiveShowSubHourSlots) && (
<span>{formatHour(hour, parseInt(minutes), (isHourStart ? effectiveHourLabelFormat : 'full') as "short" | "full", timeFormat)}</span>
<span>{formatHour(hour, parseInt(minutes), (isHourStart ? effectiveHourLabelFormat : 'full') as "short" | "full", profile.timeFormat)}</span>
)}
</div>
);
})}
{/* End-of-day 24:00 label */}
<div className="time-slot-label hour-start" style={{ height: '0px', lineHeight: 0 }}>
<span>{timeFormat === '24h' ? '24' : '12 AM'}</span>
<span>{profile.timeFormat === '24h' ? '24' : '12 AM'}</span>
</div>
</div>
</div>
@ -6629,7 +6629,7 @@ export default function WeeklyView() {
className={`weekly-days-grid cols-${viewDays}`}
data-slide-direction={slideDirection}
data-nav-type={viewDays > 1 ? "week" : "day"}
style={showTimeGrid ? {
style={profile.showTimeGrid ? {
height: `${24 * (60 / effectiveCellDuration) * getSlotHeight(effectiveCellDuration) + getHeaderHeight(effectiveCellDuration)}px`,
flex: 1,
alignSelf: "flex-start",
@ -6693,21 +6693,21 @@ export default function WeeklyView() {
<>
{activeDateLayout === "left" && (
<span className="weekly-day-date" style={dateStyle}>
{formatDateHeader(date, language)}
{formatDateHeader(date, profile.language)}
</span>
)}
<h3
className={`weekly-day-name ${isSameDay(date, new Date()) ? "is-today" : ""}`}
style={{ marginBottom: 0, flexShrink: 0 }}
>
{getDayName(date, language, weekdayFormat, customWeekdayNames, weekStartDay, weekdayCase)}
{getDayName(date, profile.language, profile.weekdayFormat, profile.customWeekdayNames, profile.weekStartDay, profile.weekdayCase)}
</h3>
{(activeDateLayout === "right" ||
activeDateLayout === "above" ||
activeDateLayout === "below" ||
activeDateLayout === undefined) && (
<span className="weekly-day-date" style={dateStyle}>
{formatDateHeader(date, language)}
{formatDateHeader(date, profile.language)}
</span>
)}
</>
@ -6717,14 +6717,14 @@ export default function WeeklyView() {
</header>
{/* Time Grid or Simple List */}
{showTimeGrid ? (
{profile.showTimeGrid ? (
<div
className="time-slots-container"
onDragLeave={handleDragLeave}
style={{ position: "relative" }}
>
{/* Calendar Fetching Indicator */}
{showTimeGrid && colIndex === 0 && isFetchingCalendar && (
{profile.showTimeGrid && colIndex === 0 && isFetchingCalendar && (
<div className="absolute top-2 left-2 z-[60] flex items-center gap-2 bg-white/90 dark:bg-zinc-800/90 px-3 py-1.5 rounded-full shadow-sm border border-zinc-200 dark:border-zinc-700 text-xs text-zinc-600 dark:text-zinc-300 pointer-events-none">
<div className="w-3 h-3 border-2 border-zinc-400 border-t-transparent rounded-full animate-spin"></div>
Syncing Calendar...
@ -7319,7 +7319,7 @@ export default function WeeklyView() {
</div>}
{/* All-Day Events Section (below position) — hidden in kanban */}
{viewStyle !== "kanban" && effectiveAllDayPosition === "below" && allDaySection}
{profile.viewStyle !== "kanban" && effectiveAllDayPosition === "below" && allDaySection}
{/* Someday Section */}
{effectiveShowSomeday && (<>
@ -8383,8 +8383,8 @@ export default function WeeklyView() {
initialStartTime={calendarEventModal.initialStartTime}
initialEndTime={calendarEventModal.initialEndTime}
connections={connections}
weekStartDay={weekStartDay}
language={language}
weekStartDay={profile.weekStartDay ?? 1}
language={profile.language}
onClose={() =>
setCalendarEventModal({ ...calendarEventModal, isOpen: false })
}
@ -8411,13 +8411,13 @@ export default function WeeklyView() {
padding: '16px 20px 12px', fontWeight: 700, fontSize: '0.95rem',
borderBottom: '2px solid #3b82f6',
}}>
{language === 'de' ? 'Wiederkehrendes Ereignis bearbeiten' : 'Edit recurring event'}
{profile.language === 'de' ? 'Wiederkehrendes Ereignis bearbeiten' : 'Edit recurring event'}
</div>
<div style={{ padding: '12px 20px' }}>
{([
{ value: 'this' as const, title: language === 'de' ? 'Nur dieses Ereignis' : 'This event', desc: language === 'de' ? 'Alle anderen Ereignisse der Serie bleiben unverändert.' : 'All other events in the series stay the same.' },
{ value: 'future' as const, title: language === 'de' ? 'Dieses und folgende Ereignisse' : 'This and following events', desc: language === 'de' ? 'Dieses und alle zukünftigen Ereignisse der Serie werden geändert.' : 'This and all future events in the series will be changed.' },
{ value: 'all' as const, title: language === 'de' ? 'Alle Ereignisse' : 'All events', desc: language === 'de' ? 'Alle Ereignisse der Serie werden geändert.' : 'All events in the series will be changed.' },
{ value: 'this' as const, title: profile.language === 'de' ? 'Nur dieses Ereignis' : 'This event', desc: profile.language === 'de' ? 'Alle anderen Ereignisse der Serie bleiben unverändert.' : 'All other events in the series stay the same.' },
{ value: 'future' as const, title: profile.language === 'de' ? 'Dieses und folgende Ereignisse' : 'This and following events', desc: profile.language === 'de' ? 'Dieses und alle zukünftigen Ereignisse der Serie werden geändert.' : 'This and all future events in the series will be changed.' },
{ value: 'all' as const, title: profile.language === 'de' ? 'Alle Ereignisse' : 'All events', desc: profile.language === 'de' ? 'Alle Ereignisse der Serie werden geändert.' : 'All events in the series will be changed.' },
]).map(opt => (
<label key={opt.value} style={{
display: 'flex', gap: 12, padding: '10px 4px', cursor: 'pointer',
@ -8444,12 +8444,12 @@ export default function WeeklyView() {
padding: '8px 16px', fontSize: '0.85rem', fontWeight: 600,
background: 'none', border: 'none', cursor: 'pointer',
color: darkMode ? '#aaa' : '#666',
}}>{language === 'de' ? 'Abbrechen' : 'Cancel'}</button>
}}>{profile.language === 'de' ? 'Abbrechen' : 'Cancel'}</button>
<button onClick={() => handleRecurringDragConfirm(recurringDragEditMode)} style={{
padding: '8px 20px', fontSize: '0.85rem', fontWeight: 600,
background: '#3b82f6', color: 'white', border: 'none',
borderRadius: 8, cursor: 'pointer',
}}>{language === 'de' ? 'Speichern' : 'Save'}</button>
}}>{profile.language === 'de' ? 'Speichern' : 'Save'}</button>
</div>
</div>
</div>
@ -8485,7 +8485,7 @@ export default function WeeklyView() {
t.dayOfWeek === now.getDay() &&
isSameDay(
currentWeekStart,
getStartOfWeek(now, weekStartDay),
getStartOfWeek(now, profile.weekStartDay ?? 1),
))),
);
@ -8520,19 +8520,19 @@ export default function WeeklyView() {
}
}}
onSettingsChanged={handleSettingsChanged}
showTimeGrid={showTimeGrid}
showTimeGrid={profile.showTimeGrid ?? true}
setShowTimeGrid={setShowTimeGrid}
cellDuration={cellDuration}
cellDuration={profile.cellDuration ?? 30}
setCellDuration={setCellDuration}
weekStartDay={weekStartDay}
weekStartDay={profile.weekStartDay ?? 1}
setWeekStartDay={setWeekStartDay}
viewStyle={viewStyle}
viewStyle={profile.viewStyle ?? "simple"}
setViewStyle={setViewStyle}
showSomeday={showSomeday}
showSomeday={profile.showSomeday ?? true}
setShowSomeday={setShowSomeday}
showAllDay={showAllDay}
showAllDay={profile.showAllDayEvents ?? true}
setShowAllDay={setShowAllDay}
showSchedule={showSchedule}
showSchedule={profile.showSchedule ?? true}
setShowSchedule={setShowSchedule}
goal={goal}
setGoal={setGoal}
@ -8543,26 +8543,26 @@ export default function WeeklyView() {
setFocusTimerDuration={setFocusTimerDuration}
focusBreakDuration={focusBreakDuration}
setFocusBreakDuration={setFocusBreakDuration}
fontSize={fontSize}
fontSize={profile.fontSize ?? "M"}
setFontSize={setFontSize}
showNextTask={showNextTask}
showNextTask={profile.showNextTask ?? false}
setShowNextTask={setShowNextTask}
headlineFont={headlineFont}
headlineFontSize={headlineFontSize}
headlineFontWeight={headlineFontWeight}
dateFontFamily={dateFontFamily}
dateFontSize={dateFontSize}
dateFontWeight={dateFontWeight}
timeTaskFontFamily={timeTaskFontFamily}
timeTaskFontSize={timeTaskFontSize}
timeTaskFontWeight={timeTaskFontWeight}
bodyFont={bodyFont}
taskFontFamily={taskFontFamily}
taskFontSize={taskFontSize}
taskFontWeight={taskFontWeight}
fontWeight={fontWeight}
weekendColorSat={weekendColorSat}
weekendColorSun={weekendColorSun}
headlineFont={profile.headlineFont ?? "Inter"}
headlineFontSize={profile.headlineFontSize ?? "1.25rem"}
headlineFontWeight={profile.headlineFontWeight ?? "900"}
dateFontFamily={profile.dateFontFamily ?? "Inter"}
dateFontSize={profile.dateFontSize ?? "0.65rem"}
dateFontWeight={profile.dateFontWeight ?? "400"}
timeTaskFontFamily={profile.timeTaskFontFamily ?? "Inter"}
timeTaskFontSize={profile.timeTaskFontSize ?? "0.75rem"}
timeTaskFontWeight={profile.timeTaskFontWeight ?? "500"}
bodyFont={profile.bodyFont ?? "Inter"}
taskFontFamily={profile.taskFontFamily ?? "Inter"}
taskFontSize={profile.taskFontSize ?? "0.9rem"}
taskFontWeight={profile.taskFontWeight ?? "400"}
fontWeight={profile.fontWeight ?? "400"}
weekendColorSat={profile.weekendColorSat ?? "#666666"}
weekendColorSun={profile.weekendColorSun ?? "#dc2626"}
protectEventTimes={protectEventTimes}
setProtectEventTimes={setProtectEventTimes}
goalFontWeight={profile.goalFontWeight || "500"}
@ -8572,11 +8572,11 @@ export default function WeeklyView() {
executeImport={executeImport}
onImportLists={(lists) => doImport("apple", lists)}
importStatusMsg={importStatusMsg}
hourLabelFormat={hourLabelFormat}
hourLabelFormat={profile.hourLabelFormat ?? "short"}
setHourLabelFormat={setHourLabelFormat}
showSubHourSlots={showSubHourSlots}
showSubHourSlots={profile.showSubHourSlots ?? true}
setShowSubHourSlots={setShowSubHourSlots}
allDayPosition={allDayPosition}
allDayPosition={profile.allDayPosition ?? "above"}
setAllDayPosition={setAllDayPosition}
saveSetting={saveSetting}
availableTaskLists={availableTaskLists}
@ -8609,7 +8609,7 @@ export default function WeeklyView() {
},
onAddProject: () => { setShowProjectsSidebar(true); },
onRecurringTasks: () => setIsRecurringTasksOpen(true),
onToggleNextTask: () => { const newVal = !showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); },
onToggleNextTask: () => { const newVal = !profile.showNextTask; setShowNextTask(newVal); saveSetting("showNextTask", newVal); },
onFocusMode: () => setShowFocusMode(true),
onToggleDarkMode: () => setDarkMode(!darkMode),
onSearch: () => setIsSearchOpen(true),
@ -8617,18 +8617,18 @@ export default function WeeklyView() {
onRedo: handleRedo,
onRefresh: () => { fetchCalendarEvents(true); fetchTasks(); },
darkMode,
showNextTask,
showNextTask: profile.showNextTask ?? false,
undoCount,
redoCount,
viewDays,
onViewDaysChange: (num: number) => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); },
showTimeGrid,
cellDuration,
showTimeGrid: profile.showTimeGrid ?? true,
cellDuration: profile.cellDuration ?? 30,
onCellDurationChange: (d: CellDuration) => { setCellDuration(d); saveSetting("cellDuration", d); },
viewStyle,
viewStyle: profile.viewStyle ?? "simple",
onViewStyleChange: (s: string) => { setViewStyle(s as any); saveSetting("viewStyle", s); },
startHour,
endHour,
startHour: profile.startHour ?? 8,
endHour: profile.endHour ?? 18,
onStartHourChange: (h: number) => { setStartHour(h); saveSetting("startHour", h); },
onEndHourChange: (h: number) => { setEndHour(h); saveSetting("endHour", h); },
}}
@ -8647,7 +8647,7 @@ export default function WeeklyView() {
task={selectedTaskForRecurrence}
onClose={() => setSelectedTaskForRecurrence(null)}
onSave={handleRecurrenceSave}
language={language}
language={profile.language}
/>
)
}
@ -8657,7 +8657,7 @@ export default function WeeklyView() {
<OnboardingWizard
profile={profile}
darkMode={darkMode}
language={language}
language={profile.language}
connections={connections}
onComplete={async () => { await saveSetting("hasCompletedOnboarding", true); setShowOnboarding(false); await fetchProfile(); }}
onSkip={async () => { await saveSetting("hasCompletedOnboarding", true); setShowOnboarding(false); await fetchProfile(); }}
@ -8808,7 +8808,7 @@ export default function WeeklyView() {
setKanbanDetailTask({ ...liveTask, markdownContent: val });
}
}}
placeholder={language === "de" ? "Notizen hinzufügen..." : "Add notes..."}
placeholder={profile.language === "de" ? "Notizen hinzufügen..." : "Add notes..."}
className="kanban-detail-notes"
rows={4}
/>
@ -8863,7 +8863,7 @@ export default function WeeklyView() {
<input
name="newSub"
type="text"
placeholder={language === "de" ? "Unteraufgabe hinzufügen..." : "Add subtask..."}
placeholder={profile.language === "de" ? "Unteraufgabe hinzufügen..." : "Add subtask..."}
className="kanban-detail-subtask-input"
/>
</form>
@ -8876,7 +8876,7 @@ export default function WeeklyView() {
<button
onClick={() => { deleteTask(liveTask.id); setKanbanDetailTask(null); }}
className="kanban-detail-delete"
title={language === "de" ? "Löschen" : "Delete"}
title={profile.language === "de" ? "Löschen" : "Delete"}
>
<Trash2 size={16} />
</button>
@ -8885,7 +8885,7 @@ export default function WeeklyView() {
onClick={() => setKanbanDetailTask(null)}
className="kanban-detail-btn kanban-detail-btn-cancel"
>
{language === "de" ? "Abbrechen" : "Cancel"}
{profile.language === "de" ? "Abbrechen" : "Cancel"}
</button>
<button
onClick={() => setKanbanDetailTask(null)}
@ -8919,7 +8919,7 @@ export default function WeeklyView() {
<div className="mobile-fab-menu">
<button className="mobile-fab-menu-item" onClick={() => { setShowMobileFabMenu(false); setShowMobileFabSheet(true); }}>
<div className="mobile-fab-menu-icon" style={{ background: "#0ea5e9" }}><Plus size={18} /></div>
<span>{language === "de" ? "Aufgabe" : "Task"}</span>
<span>{profile.language === "de" ? "Aufgabe" : "Task"}</span>
</button>
<button className="mobile-fab-menu-item" onClick={() => {
setShowMobileFabMenu(false);
@ -8927,19 +8927,19 @@ export default function WeeklyView() {
setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` });
}}>
<div className="mobile-fab-menu-icon" style={{ background: "#8b5cf6" }}><Calendar size={18} /></div>
<span>{language === "de" ? "Termin" : "Event"}</span>
<span>{profile.language === "de" ? "Termin" : "Event"}</span>
</button>
<button className="mobile-fab-menu-item" onClick={() => { setShowMobileFabMenu(false); setShowProjectsSidebar(true); }}>
<div className="mobile-fab-menu-icon" style={{ background: "#6366f1" }}><FolderPlus size={18} /></div>
<span>{language === "de" ? "Projekt" : "Project"}</span>
<span>{profile.language === "de" ? "Projekt" : "Project"}</span>
</button>
<button className="mobile-fab-menu-item" onClick={() => { setShowMobileFabMenu(false); setShowFocusMode(true); }}>
<div className="mobile-fab-menu-icon" style={{ background: "#f59e0b" }}><Zap size={18} /></div>
<span>{language === "de" ? "Fokus" : "Focus"}</span>
<span>{profile.language === "de" ? "Fokus" : "Focus"}</span>
</button>
<button className="mobile-fab-menu-item" onClick={() => { setShowMobileFabMenu(false); setIsSearchOpen(true); }}>
<div className="mobile-fab-menu-icon" style={{ background: "#10b981" }}><Search size={18} /></div>
<span>{language === "de" ? "Suchen" : "Search"}</span>
<span>{profile.language === "de" ? "Suchen" : "Search"}</span>
</button>
</div>
)}
@ -9026,7 +9026,7 @@ export default function WeeklyView() {
setShowDatePicker(false);
}}
onClose={() => setShowDatePicker(false)}
language={language}
language={profile.language}
/>
</div>
</div>