feat(design): collapsible left rail toolbar with flyout menus

Replace the overlay quick-settings panel with a persistent fixed-left
toolbar on desktop. Collapsed (44 px) shows icon-only buttons; expanded
(240 px) shows the full labelled panel. Hovering a settings icon in
collapsed mode opens a flyout panel (view, columns, slot, visibility,
appearance). Mobile overlay behaviour is unchanged.

v1.106.0
This commit is contained in:
mARTin-B78 2026-05-11 08:37:09 +02:00
parent 754fa6ad25
commit c3dd70a438
2 changed files with 213 additions and 208 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.105.2", "version": "1.106.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1249,6 +1249,10 @@ export default function WeeklyView() {
const [showDatePicker, setShowDatePicker] = useState(false); const [showDatePicker, setShowDatePicker] = useState(false);
const datePickerBtnRef = useRef<HTMLDivElement>(null); const datePickerBtnRef = useRef<HTMLDivElement>(null);
const [showQuickSettings, setShowQuickSettings] = useState(false); const [showQuickSettings, setShowQuickSettings] = useState(false);
const [leftRailExpanded, setLeftRailExpanded] = useState(false);
const [flyoutSection, setFlyoutSection] = useState<string | null>(null);
const [flyoutY, setFlyoutY] = useState(0);
const flyoutTimerRef = useRef<NodeJS.Timeout | null>(null);
const [showProjectsSidebar, setShowProjectsSidebar] = useState(false); const [showProjectsSidebar, setShowProjectsSidebar] = useState(false);
const [activeProjectFilter, setActiveProjectFilter] = useState<string | null>(null); const [activeProjectFilter, setActiveProjectFilter] = useState<string | null>(null);
const [showIntroHints, setShowIntroHints] = useState(true); const [showIntroHints, setShowIntroHints] = useState(true);
@ -5446,6 +5450,7 @@ export default function WeeklyView() {
"--weekly-past-color": activeTheme?.color8 || (darkMode "--weekly-past-color": activeTheme?.color8 || (darkMode
? invertColor(profile.pastDayColor || "#a6a6a7") ? invertColor(profile.pastDayColor || "#a6a6a7")
: profile.pastDayColor || "#a6a6a7"), : profile.pastDayColor || "#a6a6a7"),
...(!isMobile ? { paddingLeft: leftRailExpanded ? "240px" : "44px", transition: "padding-left 0.2s ease" } : {}),
} as React.CSSProperties; } as React.CSSProperties;
if (isLoading) { if (isLoading) {
@ -5475,6 +5480,39 @@ export default function WeeklyView() {
background: "none", color: dm ? "#d1d5db" : "#333", textAlign: "left" as const, background: "none", color: dm ? "#d1d5db" : "#333", textAlign: "left" as const,
}); });
const railIconBtnStyle: React.CSSProperties = {
width: "36px", height: "36px", display: "flex", alignItems: "center", justifyContent: "center",
background: "none", border: "none", cursor: "pointer", borderRadius: "8px",
color: darkMode ? "#9ca3af" : "#6b7280", flexShrink: 0,
};
const flyoutPanelStyle: React.CSSProperties = {
position: "fixed", left: "48px",
top: `${Math.min(flyoutY, (typeof window !== "undefined" ? window.innerHeight : 800) - 260)}px`,
background: darkMode ? "#1a1a2e" : "var(--paper)",
border: "1px solid var(--line)", borderRadius: "10px", padding: "12px 14px",
zIndex: 200, boxShadow: "2px 4px 16px rgba(0,0,0,0.10)", minWidth: "200px",
display: "flex", flexDirection: "column", gap: "10px",
};
const flyoutLabelStyle: React.CSSProperties = {
fontSize: "0.68rem", fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase" as const,
color: darkMode ? "#6b7280" : "var(--ink-3)", marginBottom: "2px",
};
const openFlyout = (section: string, e: React.MouseEvent) => {
if (flyoutTimerRef.current) clearTimeout(flyoutTimerRef.current);
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
setFlyoutY(rect.top);
setFlyoutSection(section);
};
const closeFlyoutDelayed = () => {
flyoutTimerRef.current = setTimeout(() => setFlyoutSection(null), 180);
};
const keepFlyoutOpen = () => {
if (flyoutTimerRef.current) clearTimeout(flyoutTimerRef.current);
};
const railSep = (
<div style={{ height: "1px", background: "var(--line)", width: "28px", margin: "3px 0", flexShrink: 0, alignSelf: "center" }} />
);
// All-Day Events Section (reusable for above/below positioning) // All-Day Events Section (reusable for above/below positioning)
const allDaySection = (() => { const allDaySection = (() => {
if (!effectiveShowAllDay) return null; if (!effectiveShowAllDay) return null;
@ -5667,36 +5705,12 @@ export default function WeeklyView() {
</div> </div>
)} )}
{/* Quick Settings Sidebar (TeuxDeux-style) */} {/* Mobile Quick Settings — full overlay triggered from mobile header */}
{showQuickSettings && ( {isMobile && showQuickSettings && (
<> <>
<div <div onClick={() => setShowQuickSettings(false)} style={{ position: "fixed", inset: 0, zIndex: 999 }} />
onClick={() => setShowQuickSettings(false)} <div style={{ position: "fixed", left: 0, top: 0, bottom: 0, width: "240px", background: darkMode ? "#1a1a2e" : "var(--paper)", borderRight: "1px solid var(--line)", zIndex: 1000, display: "flex", flexDirection: "column", padding: "16px 14px", gap: "10px", overflowY: "auto", boxShadow: "2px 0 8px rgba(0,0,0,0.04)" }}>
style={{ position: "fixed", inset: 0, zIndex: 999 }} <div style={{ fontSize: "0.85rem", fontWeight: 700, color: darkMode ? "#e5e7eb" : "#333", marginBottom: "2px" }}>{profile.language === "de" ? "Einstellungen" : "Preferences"}</div>
/>
<div
style={{
position: "fixed",
left: 0,
top: 0,
bottom: 0,
width: "240px",
background: darkMode ? "#1a1a2e" : "var(--paper)",
borderRight: "1px solid var(--line)",
zIndex: 1000,
display: "flex",
flexDirection: "column",
padding: "16px 14px",
gap: "10px",
overflowY: "auto",
boxShadow: "2px 0 8px rgba(0,0,0,0.04)",
}}
>
<div style={{ fontSize: "0.85rem", fontWeight: 700, color: darkMode ? "#e5e7eb" : "#333", marginBottom: "2px" }}>
{profile.language === "de" ? "Einstellungen" : "Preferences"}
</div>
{/* Navigation Row */}
<div style={{ display: "flex", justifyContent: "center", gap: "4px", padding: "4px 0", borderBottom: "1px solid var(--line)", paddingBottom: "10px" }}> <div style={{ display: "flex", justifyContent: "center", gap: "4px", padding: "4px 0", borderBottom: "1px solid var(--line)", paddingBottom: "10px" }}>
<button onClick={() => { goToPrevWeek(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Previous Week"><ChevronsLeft size={16} /></button> <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={() => { goToPrevDay(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Previous Day"><ChevronLeft size={16} /></button>
@ -5704,198 +5718,201 @@ export default function WeeklyView() {
<button onClick={() => { goToNextDay(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Next Day"><ChevronRight size={16} /></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> <button onClick={() => { goToNextWeek(); setShowQuickSettings(false); }} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Next Week"><ChevronsRight size={16} /></button>
</div> </div>
{/* Quick Actions */}
<div style={{ display: "flex", flexDirection: "column", gap: "1px" }}> <div style={{ display: "flex", flexDirection: "column", gap: "1px" }}>
<button onClick={() => { setIsSearchOpen(true); setShowQuickSettings(false); }} style={qsActionStyle(darkMode)}><Search size={16} /> <span>{profile.language === "de" ? "Suche" : "Search"}</span></button> <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={() => { 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={() => { 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={() => { 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={() => { 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)}> <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>
{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>{profile.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> </div>
{/* Separator */}
<div style={{ borderTop: "1px solid var(--line)", paddingTop: "8px" }} /> <div style={{ borderTop: "1px solid var(--line)", paddingTop: "8px" }} />
{/* View Style */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}> <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}> <span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Ansicht" : "View"}</span>
{profile.language === "de" ? "Ansicht" : "View"} <div style={{ display: "flex", gap: "4px" }}>{[{ key: "simple", icon: <Calendar size={13} /> }, { key: "calendar", icon: <CalendarDays size={13} /> }, { key: "list", icon: <ListTodo size={13} /> }, { key: "kanban", icon: <Kanban size={13} /> }, { key: "priority", icon: <Target size={13} /> }].map((v) => (<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } }} 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 ? "var(--accent)" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle === v.key ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{v.icon}</button>))}</div>
</span>
<div style={{ display: "flex", gap: "4px" }}>
{[
{ key: "simple", icon: <Calendar size={13} /> },
{ key: "calendar", icon: <CalendarDays size={13} /> },
{ key: "list", icon: <ListTodo size={13} /> },
{ key: "kanban", icon: <Kanban size={13} /> },
{ key: "priority", icon: <Target size={13} /> },
].map((v) => (
<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } }}
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 ? "var(--accent)" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle ===v.key ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>
{v.icon}
</button>
))}
</div>
</div> </div>
{/* Columns / Days */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}> <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}> <span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span>
{profile.language === "de" ? "Spalten" : "Days"} <div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
</span>
<div style={{ display: "flex", gap: "4px" }}>
{[1, 2, 3, 5, 7].map((num) => (
<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }}
style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>
{num}
</button>
))}
</div>
</div> </div>
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
{/* Slot Duration + sub-hour slots (only with time grid) */} {profile.showTimeGrid && (<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { const v = !effectiveShowSubHourSlots; setShowSubHourSlots(v); saveSetting("showSubHourSlots", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>)}
{profile.showTimeGrid && ( <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{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: 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>))}</div></div>
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>
{profile.language === "de" ? "Zeitfenster" : "Slot"}
</span>
<div style={{ display: "flex", gap: "4px" }}>
{(([15, 20, 30, 60] as CellDuration[])).map((d) => (
<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }}
style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>
{d}m
</button>
))}
</div>
</div>
)}
{profile.showTimeGrid && (
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? ":15/:30/:45" : ":15/:30/:45"}</span>
<button onClick={() => { const v = !effectiveShowSubHourSlots; setShowSubHourSlots(v); saveSetting("showSubHourSlots", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
)}
{/* Text size */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>
{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: 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>
))}
</div>
</div>
{/* Separator */}
<div style={{ borderTop: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingTop: "4px" }} /> <div style={{ borderTop: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingTop: "4px" }} />
{[{ label: profile.language === "de" ? "Irgendwann" : "Someday", value: effectiveShowSomeday, toggle: () => saveViewSetting("showSomeday", !effectiveShowSomeday, true) }, { label: profile.language === "de" ? "Ganztägig" : "All-day", value: effectiveShowAllDay, toggle: () => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true) }, { label: profile.language === "de" ? "Checkboxen" : "Checkboxes", value: effectiveShowTaskCheckboxes, toggle: () => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true) }, { label: profile.language === "de" ? "Projekt-Icons" : "Project Icons", value: effectiveShowProjectIcons, toggle: () => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true) }, { label: profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons", value: effectiveShowPriorityIcons, toggle: () => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true) }, ...((profile.weatherEnabled || profile.weatherLat || profile.weatherLon) ? [{ label: profile.language === "de" ? "Wetter" : "Weather", value: effectiveWeatherEnabled, toggle: () => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); } }] : []) ].map(({ label, value, toggle }) => (<div key={label} style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{label}</span><button onClick={toggle} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{value ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>))}
{/* Toggle switches */} <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><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") }}>{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") }}>{profile.language === "de" ? "Gestern" : "Yesterday"}</button></div></div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}> <div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><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} /> {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} /> {profile.language === "de" ? "Dunkel" : "Dark"}</button></div></div>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Irgendwann" : "Someday"}</span>
<button onClick={() => saveViewSetting("showSomeday", !effectiveShowSomeday, true)} 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" }}>{profile.language === "de" ? "Ganztägig" : "All-day"}</span>
<button onClick={() => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true)} 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" }}>{profile.language === "de" ? "Checkboxen" : "Checkboxes"}</span>
<button onClick={() => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true)} 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" }}>{profile.language === "de" ? "Projekt-Icons" : "Project Icons"}</span>
<button onClick={() => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true)} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowProjectIcons ? <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" }}>{profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons"}</span>
<button onClick={() => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true)} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveShowPriorityIcons ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
{(profile.weatherEnabled || profile.weatherLat || profile.weatherLon) && (
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{profile.language === "de" ? "Wetter" : "Weather"}</span>
<button onClick={() => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>
{effectiveWeatherEnabled ? <Eye size={16} /> : <EyeOff size={16} />}
</button>
</div>
)}
{/* Start on */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<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") }}>
{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") }}>
{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" }}>{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} /> {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} /> {profile.language === "de" ? "Dunkel" : "Dark"}
</button>
</div>
</div>
{/* Separator */}
<div style={{ borderTop: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingTop: "4px" }} /> <div style={{ borderTop: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingTop: "4px" }} />
{/* Undo / Redo / Refresh */}
<div style={{ display: "flex", justifyContent: "center", gap: "8px", padding: "2px 0" }}> <div style={{ display: "flex", justifyContent: "center", gap: "8px", padding: "2px 0" }}>
<button onClick={handleUndo} disabled={undoCount === 0} style={{ ...qsBtnStyle(darkMode), opacity: undoCount === 0 ? 0.3 : 1 }} title="Undo"><Undo2 size={15} /></button> <button onClick={handleUndo} disabled={undoCount === 0} style={{ ...qsBtnStyle(darkMode), opacity: undoCount === 0 ? 0.3 : 1 }} title="Undo"><Undo2 size={15} /></button>
<button onClick={handleRedo} disabled={redoCount === 0} style={{ ...qsBtnStyle(darkMode), opacity: redoCount === 0 ? 0.3 : 1 }} title="Redo"><Redo2 size={15} /></button> <button onClick={handleRedo} disabled={redoCount === 0} style={{ ...qsBtnStyle(darkMode), opacity: redoCount === 0 ? 0.3 : 1 }} title="Redo"><Redo2 size={15} /></button>
<button onClick={() => { fetchCalendarEvents(true); fetchTasks(); setShowQuickSettings(false); }} style={qsBtnStyle(darkMode)} title="Refresh"><RefreshCcw size={15} /></button> <button onClick={() => { fetchCalendarEvents(true); fetchTasks(); setShowQuickSettings(false); }} style={qsBtnStyle(darkMode)} title="Refresh"><RefreshCcw size={15} /></button>
</div> </div>
{/* Spacer */}
<div style={{ flex: 1 }} /> <div style={{ flex: 1 }} />
<button onClick={() => setShowQuickSettings(false)} style={{ display: "flex", alignItems: "center", gap: "6px", background: "none", border: "none", cursor: "pointer", fontSize: "0.75rem", color: darkMode ? "#6b7280" : "#9ca3af", padding: "4px 0" }}>
{/* Close button at bottom */} <PanelLeftClose size={16} />{profile.language === "de" ? "Ausblenden" : "Hide"}
<button
onClick={() => setShowQuickSettings(false)}
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} />
{profile.language === "de" ? "Ausblenden" : "Hide"}
</button> </button>
</div> </div>
</> </>
)} )}
{/* Desktop Left Rail — persistent, collapses to icon strip */}
{!isMobile && (
<>
{/* The rail panel */}
<div style={{ position: "fixed", left: 0, top: 0, bottom: 0, width: leftRailExpanded ? "240px" : "44px", transition: "width 0.2s ease", background: darkMode ? "#1a1a2e" : "var(--paper)", borderRight: "1px solid var(--line)", zIndex: 100, display: "flex", flexDirection: "column", overflow: "hidden" }}>
{leftRailExpanded ? (
// ── EXPANDED MODE ──
<div style={{ padding: "16px 14px", display: "flex", flexDirection: "column", gap: "10px", overflowY: "auto", flex: 1 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "0.85rem", fontWeight: 700, color: darkMode ? "#e5e7eb" : "#333" }}>{profile.language === "de" ? "Einstellungen" : "Preferences"}</span>
<button onClick={() => setLeftRailExpanded(false)} style={{ background: "none", border: "none", cursor: "pointer", color: darkMode ? "#6b7280" : "#9ca3af", padding: "2px", display: "flex" }} title="Collapse">
<PanelLeftClose size={16} />
</button>
</div>
{/* Navigation */}
<div style={{ display: "flex", justifyContent: "center", gap: "4px", padding: "4px 0", borderBottom: "1px solid var(--line)", paddingBottom: "10px" }}>
<button onClick={goToPrevWeek} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Previous Week"><ChevronsLeft size={16} /></button>
<button onClick={goToPrevDay} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Previous Day"><ChevronLeft size={16} /></button>
<button onClick={goToToday} style={{ ...qsBtnStyle(darkMode), minWidth: "56px", fontWeight: 700, fontSize: "0.7rem" }}>{profile.language === "de" ? "Heute" : "Today"}</button>
<button onClick={goToNextDay} style={{ ...qsBtnStyle(darkMode), minWidth: "36px" }} title="Next Day"><ChevronRight size={16} /></button>
<button onClick={goToNextWeek} 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)} style={qsActionStyle(darkMode)}><Search size={16} /> <span>{profile.language === "de" ? "Suche" : "Search"}</span></button>
<button onClick={() => setShowDatePicker(true)} 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` }); }} style={qsActionStyle(darkMode)}><Plus size={16} /> <span>{profile.language === "de" ? "Kalendereintrag" : "Calendar Event"}</span></button>
<button onClick={() => setShowProjectsSidebar(true)} style={qsActionStyle(darkMode)}><FolderPlus size={16} /> <span>{profile.language === "de" ? "Projekte" : "Projects"}</span></button>
<button onClick={() => setIsRecurringTasksOpen(true)} 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)} style={qsActionStyle(darkMode)}><Zap size={16} /> <span>{profile.language === "de" ? "Fokus" : "Focus"}</span></button>
</div>
<div style={{ borderTop: "1px solid var(--line)", paddingTop: "8px" }} />
{/* View Style */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Ansicht" : "View"}</span>
<div style={{ display: "flex", gap: "4px" }}>{[{ key: "simple", icon: <Calendar size={13} /> }, { key: "calendar", icon: <CalendarDays size={13} /> }, { key: "list", icon: <ListTodo size={13} /> }, { key: "kanban", icon: <Kanban size={13} /> }, { key: "priority", icon: <Target size={13} /> }].map((v) => (<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } }} 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 ? "var(--accent)" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle === v.key ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{v.icon}</button>))}</div>
</div>
{/* Columns / Days */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Spalten" : "Days"}</span>
<div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
</div>
{/* Slot Duration */}
{profile.showTimeGrid && (<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "var(--ink-3)" }}>{profile.language === "de" ? "Zeitfenster" : "Slot"}</span><div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div></div>)}
{profile.showTimeGrid && (<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { const v = !effectiveShowSubHourSlots; setShowSubHourSlots(v); saveSetting("showSubHourSlots", v); }} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>)}
{/* Text size */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><span style={{ fontSize: "0.7rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{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: 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>))}</div></div>
<div style={{ borderTop: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingTop: "4px" }} />
{/* Visibility toggles */}
{[{ label: profile.language === "de" ? "Irgendwann" : "Someday", value: effectiveShowSomeday, toggle: () => saveViewSetting("showSomeday", !effectiveShowSomeday, true) }, { label: profile.language === "de" ? "Ganztägig" : "All-day", value: effectiveShowAllDay, toggle: () => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true) }, { label: profile.language === "de" ? "Checkboxen" : "Checkboxes", value: effectiveShowTaskCheckboxes, toggle: () => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true) }, { label: profile.language === "de" ? "Projekt-Icons" : "Project Icons", value: effectiveShowProjectIcons, toggle: () => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true) }, { label: profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons", value: effectiveShowPriorityIcons, toggle: () => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true) }, ...((profile.weatherEnabled || profile.weatherLat || profile.weatherLon) ? [{ label: profile.language === "de" ? "Wetter" : "Weather", value: effectiveWeatherEnabled, toggle: () => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); } }] : []) ].map(({ label, value, toggle }) => (<div key={label} style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", fontWeight: 600, color: darkMode ? "#9ca3af" : "#6b7280" }}>{label}</span><button onClick={toggle} style={{ background: "none", border: "none", cursor: "pointer", padding: "2px", color: darkMode ? "#9ca3af" : "#6b7280" }}>{value ? <Eye size={16} /> : <EyeOff size={16} />}</button></div>))}
{/* Start on */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><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") }}>{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") }}>{profile.language === "de" ? "Gestern" : "Yesterday"}</button></div></div>
{/* Display */}
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}><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} /> {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} /> {profile.language === "de" ? "Dunkel" : "Dark"}</button></div></div>
<div style={{ borderTop: `1px solid ${darkMode ? "#333" : "#e5e7eb"}`, paddingTop: "4px" }} />
{/* Undo / Redo / Refresh */}
<div style={{ display: "flex", justifyContent: "center", gap: "8px", padding: "2px 0" }}>
<button onClick={handleUndo} disabled={undoCount === 0} style={{ ...qsBtnStyle(darkMode), opacity: undoCount === 0 ? 0.3 : 1 }} title="Undo"><Undo2 size={15} /></button>
<button onClick={handleRedo} disabled={redoCount === 0} style={{ ...qsBtnStyle(darkMode), opacity: redoCount === 0 ? 0.3 : 1 }} title="Redo"><Redo2 size={15} /></button>
<button onClick={() => { fetchCalendarEvents(true); fetchTasks(); }} style={qsBtnStyle(darkMode)} title="Refresh"><RefreshCcw size={15} /></button>
<button onClick={() => setShowWeekPrintModal(true)} style={qsBtnStyle(darkMode)} title={profile.language === "de" ? "Drucken" : "Print"}><Printer size={15} /></button>
</div>
<div style={{ flex: 1 }} />
</div>
) : (
// ── COLLAPSED MODE — icon strip ──
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", paddingTop: "6px", paddingBottom: "8px", height: "100%", overflowY: "auto", overflowX: "visible" }}>
{/* Expand toggle */}
<button onClick={() => setLeftRailExpanded(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Erweitern" : "Expand"}><PanelLeftOpen size={16} /></button>
{railSep}
{/* Navigation */}
<button onClick={goToPrevWeek} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Vorherige Woche" : "Previous Week"}><ChevronsLeft size={15} /></button>
<button onClick={goToPrevDay} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Vorheriger Tag" : "Previous Day"}><ChevronLeft size={15} /></button>
<button onClick={goToToday} style={{ ...railIconBtnStyle, fontSize: "0.55rem", fontWeight: 800, letterSpacing: "0.05em" }} title="Today">{profile.language === "de" ? "HEUTE" : "TODAY"}</button>
<button onClick={goToNextDay} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Nächster Tag" : "Next Day"}><ChevronRight size={15} /></button>
<button onClick={goToNextWeek} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Nächste Woche" : "Next Week"}><ChevronsRight size={15} /></button>
{railSep}
{/* View flyout */}
<button style={{ ...railIconBtnStyle, color: "var(--accent)" }} title={profile.language === "de" ? "Ansicht" : "View"} onMouseEnter={(e) => openFlyout("view", e)} onMouseLeave={closeFlyoutDelayed}>
{profile.viewStyle === "simple" ? <Calendar size={15} /> : profile.viewStyle === "calendar" ? <CalendarDays size={15} /> : profile.viewStyle === "list" ? <ListTodo size={15} /> : profile.viewStyle === "kanban" ? <Kanban size={15} /> : <Target size={15} />}
</button>
{/* Columns flyout */}
<button style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Spalten" : "Columns"} onMouseEnter={(e) => openFlyout("columns", e)} onMouseLeave={closeFlyoutDelayed}><LayoutGrid size={15} /></button>
{/* Slot flyout */}
{profile.showTimeGrid && (<button style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Zeitfenster" : "Slot"} onMouseEnter={(e) => openFlyout("slot", e)} onMouseLeave={closeFlyoutDelayed}><Clock size={15} /></button>)}
{railSep}
{/* Quick Actions */}
<button onClick={() => setIsSearchOpen(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Suche" : "Search"}><Search size={15} /></button>
<button onClick={() => setShowDatePicker(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Datum wählen" : "Jump to date"}><CalendarDays size={15} /></button>
<button onClick={() => { const now = new Date(); setCalendarEventModal({ isOpen: true, event: undefined, initialDate: now, initialStartTime: `${String(now.getHours()).padStart(2, "0")}:00` }); }} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Kalendereintrag" : "Calendar Event"}><Plus size={15} /></button>
<button onClick={() => setShowProjectsSidebar(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Projekte" : "Projects"}><FolderPlus size={15} /></button>
<button onClick={() => setIsRecurringTasksOpen(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Wiederkehrend" : "Recurring"}><Repeat size={15} /></button>
<button onClick={() => { const nv = !profile.showNextTask; setShowNextTask(nv); saveSetting("showNextTask", nv); }} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Ziel" : "Goal"}>{profile.showNextTask ? <Play size={15} /> : <Target size={15} />}</button>
<button onClick={() => setShowFocusMode(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Fokus" : "Focus"}><Zap size={15} /></button>
{railSep}
{/* Visibility flyout */}
<button style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Sichtbarkeit" : "Visibility"} onMouseEnter={(e) => openFlyout("visibility", e)} onMouseLeave={closeFlyoutDelayed}><Eye size={15} /></button>
{/* Appearance flyout */}
<button style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Darstellung" : "Appearance"} onMouseEnter={(e) => openFlyout("display", e)} onMouseLeave={closeFlyoutDelayed}>{darkMode ? <Moon size={15} /> : <Sun size={15} />}</button>
{railSep}
{/* History & Utilities */}
<button onClick={handleUndo} disabled={undoCount === 0} style={{ ...railIconBtnStyle, opacity: undoCount === 0 ? 0.3 : 1 }} title="Undo"><Undo2 size={15} /></button>
<button onClick={handleRedo} disabled={redoCount === 0} style={{ ...railIconBtnStyle, opacity: redoCount === 0 ? 0.3 : 1 }} title="Redo"><Redo2 size={15} /></button>
<button onClick={() => { fetchCalendarEvents(true); fetchTasks(); }} style={{ ...railIconBtnStyle }} title="Refresh"><RefreshCcw size={15} /></button>
<button onClick={() => setShowWeekPrintModal(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Drucken" : "Print"}><Printer size={15} /></button>
<button onClick={() => setDarkMode(!darkMode)} style={{ ...railIconBtnStyle }} title={darkMode ? "Light Mode" : "Dark Mode"}>{darkMode ? <Sun size={15} /> : <Moon size={15} />}</button>
<div style={{ flex: 1 }} />
{railSep}
{/* Settings / User at bottom */}
<button onClick={() => setShowSettings(true)} style={{ ...railIconBtnStyle }} title={profile.language === "de" ? "Einstellungen" : "Settings"}><User size={15} /></button>
</div>
)}
</div>
{/* Flyout panels — rendered outside the rail so they can overflow */}
{!leftRailExpanded && flyoutSection && (
<div style={{ ...flyoutPanelStyle }} onMouseEnter={keepFlyoutOpen} onMouseLeave={closeFlyoutDelayed}>
{flyoutSection === "view" && (
<>
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Ansicht" : "View"}</div>
<div style={{ display: "flex", gap: "4px" }}>{[{ key: "simple", icon: <Calendar size={13} />, label: profile.language === "de" ? "Einfach" : "Simple" }, { key: "calendar", icon: <CalendarDays size={13} />, label: profile.language === "de" ? "Kalender" : "Calendar" }, { key: "list", icon: <ListTodo size={13} />, label: profile.language === "de" ? "Liste" : "List" }, { key: "kanban", icon: <Kanban size={13} />, label: "Kanban" }, { key: "priority", icon: <Target size={13} />, label: profile.language === "de" ? "Priorität" : "Priority" }].map((v) => (<button key={v.key} onClick={() => { setViewStyle(v.key as any); saveSetting("viewStyle", v.key); if (v.key === "list") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } if (v.key === "simple" || v.key === "calendar") { setShowTimeGrid(true); saveSetting("showTimeGrid", true); } if (v.key === "priority" || v.key === "kanban") { setShowTimeGrid(false); saveSetting("showTimeGrid", false); } setFlyoutSection(null); }} title={v.label} style={{ flex: 1, padding: "6px 0", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", background: profile.viewStyle === v.key ? "var(--accent)" : (darkMode ? "#1f2937" : "#e5e7eb"), color: profile.viewStyle === v.key ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{v.icon}</button>))}</div>
</>
)}
{flyoutSection === "columns" && (
<>
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Spalten" : "Days"}</div>
<div style={{ display: "flex", gap: "4px" }}>{[1, 2, 3, 5, 7].map((num) => (<button key={num} onClick={() => { setViewDays(num); savedViewDaysRef.current = num; saveSetting("viewDays", num); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: viewDays === num ? 700 : 400, background: viewDays === num ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: viewDays === num ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{num}</button>))}</div>
</>
)}
{flyoutSection === "slot" && (
<>
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Zeitfenster" : "Slot Duration"}</div>
<div style={{ display: "flex", gap: "4px" }}>{(([15, 20, 30, 60] as CellDuration[])).map((d) => (<button key={d} onClick={() => { setCellDuration(d); saveSetting("cellDuration", d); saveViewSetting("cellDuration", d, true); }} style={{ flex: 1, padding: "5px 0", fontSize: "0.8rem", borderRadius: "6px", border: "none", cursor: "pointer", fontWeight: effectiveCellDuration === d ? 700 : 400, background: effectiveCellDuration === d ? (darkMode ? "#374151" : "var(--ink)") : (darkMode ? "#1f2937" : "#e5e7eb"), color: effectiveCellDuration === d ? "#fff" : (darkMode ? "#9ca3af" : "var(--ink-3)") }}>{d}m</button>))}</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}><span style={{ fontSize: "0.75rem", color: darkMode ? "#9ca3af" : "#6b7280" }}>:15/:30/:45</span><button onClick={() => { const v = !effectiveShowSubHourSlots; setShowSubHourSlots(v); saveSetting("showSubHourSlots", v); }} style={{ background: "none", border: "none", cursor: "pointer", color: darkMode ? "#9ca3af" : "#6b7280" }}>{effectiveShowSubHourSlots ? <Eye size={15} /> : <EyeOff size={15} />}</button></div>
</>
)}
{flyoutSection === "visibility" && (
<>
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Sichtbarkeit" : "Visibility"}</div>
{[{ label: profile.language === "de" ? "Irgendwann" : "Someday", value: effectiveShowSomeday, toggle: () => saveViewSetting("showSomeday", !effectiveShowSomeday, true) }, { label: profile.language === "de" ? "Ganztägig" : "All-day", value: effectiveShowAllDay, toggle: () => saveViewSetting("showAllDayEvents", !effectiveShowAllDay, true) }, { label: profile.language === "de" ? "Checkboxen" : "Checkboxes", value: effectiveShowTaskCheckboxes, toggle: () => saveViewSetting("showTaskCheckboxes", !effectiveShowTaskCheckboxes, true) }, { label: profile.language === "de" ? "Projekt-Icons" : "Project Icons", value: effectiveShowProjectIcons, toggle: () => saveViewSetting("showProjectIcons", !effectiveShowProjectIcons, true) }, { label: profile.language === "de" ? "Prioritäts-Icons" : "Priority Icons", value: effectiveShowPriorityIcons, toggle: () => saveViewSetting("showPriorityIcons", !effectiveShowPriorityIcons, true) }, ...((profile.weatherEnabled || profile.weatherLat || profile.weatherLon) ? [{ label: profile.language === "de" ? "Wetter" : "Weather", value: effectiveWeatherEnabled, toggle: () => { const v = !effectiveWeatherEnabled; setProfile((p: any) => ({ ...p, weatherEnabled: v })); saveViewSetting("weatherEnabled", v, true); } }] : []) ].map(({ label, value, toggle }) => (<div key={label} style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: "16px" }}><span style={{ fontSize: "0.8rem", color: darkMode ? "#d1d5db" : "#333", whiteSpace: "nowrap" }}>{label}</span><button onClick={toggle} style={{ background: "none", border: "none", cursor: "pointer", color: darkMode ? "#9ca3af" : "#6b7280", flexShrink: 0 }}>{value ? <Eye size={15} /> : <EyeOff size={15} />}</button></div>))}
</>
)}
{flyoutSection === "display" && (
<>
<div style={flyoutLabelStyle}>{profile.language === "de" ? "Darstellung" : "Appearance"}</div>
<div><div style={{ fontSize: "0.7rem", color: darkMode ? "#9ca3af" : "#6b7280", marginBottom: "4px" }}>{profile.language === "de" ? "Textgröße" : "Text size"}</div><div style={{ display: "flex", gap: "4px" }}>{(["S", "M", "L"] as const).map((size) => (<button key={size} onClick={() => { setFontSize(size); saveSetting("fontSize", size); }} style={{ flex: 1, padding: "4px 0", 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>))}</div></div>
<div><div style={{ fontSize: "0.7rem", color: darkMode ? "#9ca3af" : "#6b7280", marginBottom: "4px" }}>{profile.language === "de" ? "Starten mit" : "Start on"}</div><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={{ flex: 1, padding: "4px 0", 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") }}>{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={{ flex: 1, padding: "4px 0", 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") }}>{profile.language === "de" ? "Gestern" : "Yesterday"}</button></div></div>
<div><div style={{ fontSize: "0.7rem", color: darkMode ? "#9ca3af" : "#6b7280", marginBottom: "4px" }}>{profile.language === "de" ? "Anzeige" : "Display"}</div><div style={{ display: "flex", gap: "4px" }}><button onClick={() => setDarkMode(false)} style={{ flex: 1, padding: "4px 0", fontSize: "0.75rem", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: "4px", fontWeight: !darkMode ? 700 : 400, background: !darkMode ? "#333" : (darkMode ? "#1f2937" : "#e5e7eb"), color: !darkMode ? "#fff" : (darkMode ? "#9ca3af" : "#6b7280") }}><Sun size={12} /> {profile.language === "de" ? "Hell" : "Light"}</button><button onClick={() => setDarkMode(true)} style={{ flex: 1, padding: "4px 0", fontSize: "0.75rem", borderRadius: "6px", border: "none", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: "4px", fontWeight: darkMode ? 700 : 400, background: darkMode ? "#374151" : "#e5e7eb", color: darkMode ? "#fff" : "#6b7280" }}><Moon size={12} /> {profile.language === "de" ? "Dunkel" : "Dark"}</button></div></div>
</>
)}
</div>
)}
</>
)}
{/* Projects Sidebar */} {/* Projects Sidebar */}
{showProjectsSidebar && ( {showProjectsSidebar && (
<ProjectsSidebar <ProjectsSidebar
@ -5910,8 +5927,6 @@ export default function WeeklyView() {
/> />
)} )}
{/* Quick Settings toggle button is now in the desktop header toolbar */}
{/* View Transitions Style Block */} {/* View Transitions Style Block */}
<style <style
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
@ -5999,16 +6014,6 @@ export default function WeeklyView() {
<header className="group relative flex items-center justify-between w-full px-4 py-1.5 border-b bg-white dark:bg-gray-900 dark:text-white transition-colors duration-200" style={isMobile ? { display: "none" } : { borderBottomColor: 'var(--line)' }}> <header className="group relative flex items-center justify-between w-full px-4 py-1.5 border-b bg-white dark:bg-gray-900 dark:text-white transition-colors duration-200" style={isMobile ? { display: "none" } : { borderBottomColor: 'var(--line)' }}>
{/* LEFT SECTION: View Switcher, Days, Hours, Slot Duration */} {/* LEFT SECTION: View Switcher, Days, Hours, Slot Duration */}
<div className="weekly-header-controls flex items-center gap-4 transition-opacity duration-700 opacity-0 group-hover:opacity-100" style={{ zIndex: 1, ...(showIntroHints ? { opacity: 1 } : {}) }}> <div className="weekly-header-controls flex items-center gap-4 transition-opacity duration-700 opacity-0 group-hover:opacity-100" style={{ zIndex: 1, ...(showIntroHints ? { opacity: 1 } : {}) }}>
{/* Quick Settings toggle */}
{!showQuickSettings && (
<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={profile.language === "de" ? "Schnelleinstellungen" : "Quick Settings"}
>
<PanelLeftOpen size={16} />
</button>
)}
{/* View Switcher */} {/* View Switcher */}
<div className="flex items-center gap-0.5 bg-gray-100 dark:bg-gray-800 rounded p-0.5" title={t.viewStyle}> <div className="flex items-center gap-0.5 bg-gray-100 dark:bg-gray-800 rounded p-0.5" title={t.viewStyle}>
<button <button