diff --git a/package.json b/package.json index 1dea7e7..e29d4d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.74.8", + "version": "1.75.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index e052c34..6a6714f 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1873,6 +1873,8 @@ export default function WeeklyView() { const [showMobileFabSheet, setShowMobileFabSheet] = useState(false); const [showMobileFabMenu, setShowMobileFabMenu] = useState(false); + const [mobileStickyDay, setMobileStickyDay] = useState(null); + const [mobileStickyDayVisible, setMobileStickyDayVisible] = useState(false); const [showHeaderMore, setShowHeaderMore] = useState(false); const [fabTaskTitle, setFabTaskTitle] = useState(""); @@ -3079,6 +3081,68 @@ export default function WeeklyView() { return () => clearInterval(interval); }, []); + // Mobile: track which day column is at the top of the viewport via IntersectionObserver + useEffect(() => { + if (!isMobile || !gridRef.current) return; + const headers = gridRef.current.querySelectorAll('.weekly-day-header'); + if (!headers.length) return; + + const observer = new IntersectionObserver( + (entries) => { + // Find the last header that is intersecting (at the top of viewport) + let topHeader: Element | null = null; + let topY = Infinity; + entries.forEach(entry => { + if (entry.isIntersecting || entry.boundingClientRect.top < 100) { + if (entry.boundingClientRect.top < topY) { + topY = entry.boundingClientRect.top; + topHeader = entry.target; + } + } + }); + + // Also check which header is closest to top when scrolled past + if (!topHeader) { + headers.forEach(h => { + const rect = h.getBoundingClientRect(); + if (rect.top < 100 && rect.top > topY - 200) { + topY = rect.top; + topHeader = h; + } + }); + } + + if (topHeader) { + const col = (topHeader as Element).closest('.weekly-day-column'); + const dateStr = col?.getAttribute('data-date'); + if (dateStr) { + const d = new Date(dateStr + 'T00:00:00'); + const dayNames = language === 'de' + ? ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'] + : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + const label = `${dayNames[d.getDay()]}, ${d.getDate()}.${d.getMonth() + 1}.`; + setMobileStickyDay(label); + } + } + }, + { threshold: [0, 0.1, 0.5, 1], rootMargin: '-48px 0px 0px 0px' } + ); + + headers.forEach(h => observer.observe(h)); + return () => observer.disconnect(); + }, [isMobile, currentWeekStart, viewDays, language]); + + // Show/hide sticky day bar based on scroll position + useEffect(() => { + if (!isMobile) return; + const handleScroll = () => { + setMobileStickyDayVisible(window.scrollY > 80); + }; + window.addEventListener('scroll', handleScroll, { passive: true }); + handleScroll(); + return () => window.removeEventListener('scroll', handleScroll); + }, [isMobile]); + // Compute the goal date key: for "week" scope, normalize to Monday of that week; for "day", use the exact date const getGoalDateKey = useCallback( (date: Date): string => { @@ -6291,6 +6355,33 @@ export default function WeeklyView() { className={`weekly-container ${darkMode ? "dark-mode" : ""} font-size-${fontSize.toLowerCase()} ${viewStyle}-view ${showTimeGrid ? "time-grid-on" : "time-grid-off"}`} style={containerStyle} > + {/* Mobile sticky day indicator */} + {isMobile && mobileStickyDay && mobileStickyDayVisible && ( +
+ {mobileStickyDay} +
+ )} + {/* Quick Settings Sidebar (TeuxDeux-style) */} {showQuickSettings && ( <>