diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a0a86fa..cb75da6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -39,6 +39,7 @@ model User { showSchedule Boolean @default(true) cellDuration Int @default(30) viewStyle String @default("grid") + viewDays Int @default(7) fontSize String @default("M") // "S", "M", "L" headlineFont String @default("Inter") headlineFontSize String? @default("1.25rem") diff --git a/src/app/api/user/profile/route.ts b/src/app/api/user/profile/route.ts index b59a338..1b0361c 100644 --- a/src/app/api/user/profile/route.ts +++ b/src/app/api/user/profile/route.ts @@ -32,6 +32,7 @@ export async function GET(request: NextRequest) { showSchedule: true, cellDuration: true, viewStyle: true, + viewDays: true, fontSize: true, headlineFont: true, headlineFontSize: true, @@ -83,7 +84,7 @@ export async function PATCH(request: NextRequest) { language, dateFormat, timeFormat, startHour, endHour, showNextTask, calendarEditMode, focusTimerDuration, focusBreakDuration, showTimeGrid, showSomeday, showAllDayEvents, showSchedule, - cellDuration, viewStyle, fontSize, + cellDuration, viewStyle, viewDays, fontSize, headlineFont, headlineFontSize, headlineFontWeight, dateFontFamily, dateFontSize, dateFontWeight, timeTaskFontFamily, timeTaskFontSize, timeTaskFontWeight, @@ -114,6 +115,7 @@ export async function PATCH(request: NextRequest) { ...(showSchedule !== undefined && { showSchedule }), ...(cellDuration !== undefined && !isNaN(cellDuration) && { cellDuration }), ...(viewStyle !== undefined && { viewStyle }), + ...(viewDays !== undefined && !isNaN(viewDays) && { viewDays }), ...(fontSize !== undefined && { fontSize }), ...(headlineFont !== undefined && { headlineFont }), ...(headlineFontSize !== undefined && { headlineFontSize }), @@ -169,6 +171,7 @@ export async function PATCH(request: NextRequest) { showSchedule: true, cellDuration: true, viewStyle: true, + viewDays: true, fontSize: true, headlineFont: true, headlineFontSize: true, diff --git a/src/app/globals.css b/src/app/globals.css index b710e05..3643d6a 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1013,12 +1013,20 @@ h3 { } .weekly-someday-lists-grid::-webkit-scrollbar-thumb { - background-color: rgba(0, 0, 0, 0.1); + background-color: rgba(0, 0, 0, 0.2); border-radius: 4px; } +.dark-mode .weekly-someday-lists-grid::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); +} + .weekly-someday-lists-grid::-webkit-scrollbar-thumb:hover { - background-color: rgba(0, 0, 0, 0.2); + background-color: rgba(0, 0, 0, 0.3); +} + +.dark-mode .weekly-someday-lists-grid::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.3); } /* Remove grid column classes as we are using flex now */ @@ -1060,6 +1068,36 @@ h3 { background-position: 0 40px; /* Offset for the header */ } +.someday-drag-handle { + cursor: grab; + color: #ccc; + padding: 2px; + margin-right: 4px; + display: flex; + align-items: center; + border-radius: 3px; + transition: background-color 0.2s, color 0.2s; +} + +.someday-drag-handle:hover { + background: rgba(0, 0, 0, 0.05); + color: #888; +} + +.dark-mode .someday-drag-handle:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +.someday-list-delete-btn { + opacity: 0; + transition: opacity 0.2s; +} + +.weekly-someday-list-title-header:hover .someday-list-delete-btn { + opacity: 1; +} + /* Placeholder styling */ .weekly-someday-list.placeholder-list { background-image: repeating-linear-gradient( @@ -1626,6 +1664,8 @@ h3 { border-radius: 0 4px 4px 0; margin: 1px 0; overflow: hidden; + left: -10px; + right: -15px; } .time-slot-event .event-title-row { @@ -1738,6 +1778,13 @@ h3 { padding: 0 0.25rem; min-height: 28px; border-right: 1px solid var(--weekly-border); + overflow-y: auto; + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE/Edge */ +} + +.all-day-events-column::-webkit-scrollbar { + display: none; /* Chrome/Safari */ } .all-day-events-column:last-child { diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index e91561a..f38d064 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -21,7 +21,8 @@ import { Target, Sun, Moon, - Repeat + Repeat, + GripVertical } from 'lucide-react'; // Types @@ -354,7 +355,12 @@ export default function WeeklyView() { return { ...event, editable: isEditable }; }); }, [rawCalendarEvents, connections]); - const [currentWeekStart, setCurrentWeekStart] = useState(getStartOfWeek(new Date(), 1)); // Default align to Monday initially + const [currentWeekStart, setCurrentWeekStart] = useState(() => { + const d = new Date(); + d.setHours(0, 0, 0, 0); + d.setDate(d.getDate() - 1); + return d; + }); const [viewDays, setViewDays] = useState(7); const [isLoading, setIsLoading] = useState(true); const [darkMode, setDarkMode] = useState(false); @@ -571,8 +577,9 @@ export default function WeeklyView() { useEffect(() => { if (!mounted) return; localStorage.setItem('weekly-week-start', String(weekStartDay)); - // Re-align current week start when start day changes - setCurrentWeekStart(prev => getStartOfWeek(prev, weekStartDay)); + // 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]); // Translation helper @@ -766,44 +773,24 @@ export default function WeeklyView() { return () => clearInterval(interval); }, []); - const handleSettingsChanged = (newSettings: { - showTimeGrid: boolean; - cellDuration: CellDuration; - viewStyle: 'grid' | 'list'; - language: string; - dateFormat: string; - timeFormat: string; - startHour: number; - endHour: number; - fontSize: 'S' | 'M' | 'L'; - showNextTask: boolean; - showSomeday: boolean; - showAllDayEvents: boolean; - showSchedule: boolean; - headlineFont: string; - headlineFontSize: string; - headlineFontWeight: string; - dateFontFamily: string; - dateFontSize: string; - dateFontWeight: string; - timeTaskFontFamily: string; - timeTaskFontSize: string; - timeTaskFontWeight: string; - bodyFont: string; - taskFontFamily: string; - taskFontSize: string; - taskFontWeight: string; - eventFontFamily?: string; - eventFontSize?: string; - eventFontWeight?: string; - fontWeight?: string; - weekendColorSat?: string; - weekendColorSun?: string; - weekdayColor?: string; - dateColor?: string; - taskColor?: string; - todayHighlightColor?: string; - }) => { + const handleSomedayWheel = (e: React.WheelEvent) => { + if (e.currentTarget) { + e.currentTarget.scrollLeft += e.deltaY; + } + }; + const saveSetting = async (key: string, value: any) => { + try { + await fetch('/api/user/profile', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ [key]: value }) + }); + } catch (err) { + console.error(`Failed to save setting ${key}:`, err); + } + }; + + const handleSettingsChanged = (newSettings: any) => { setShowTimeGrid(newSettings.showTimeGrid); setCellDuration(newSettings.cellDuration); setViewStyle(newSettings.viewStyle); @@ -837,15 +824,13 @@ export default function WeeklyView() { if (newSettings.weekendColorSat) setWeekendColorSat(newSettings.weekendColorSat); if (newSettings.weekendColorSun) setWeekendColorSun(newSettings.weekendColorSun); - // eslint-disable-next-line @typescript-eslint/no-explicit-any setProfile((prev: any) => ({ ...prev, + ...newSettings, weekdayColor: newSettings.weekdayColor || prev.weekdayColor, dateColor: newSettings.dateColor || prev.dateColor, taskColor: newSettings.taskColor || prev.taskColor, todayHighlightColor: newSettings.todayHighlightColor || prev.todayHighlightColor, - weekendColorSat: newSettings.weekendColorSat, - weekendColorSun: newSettings.weekendColorSun, eventFontFamily: newSettings.eventFontFamily || prev.eventFontFamily, eventFontSize: newSettings.eventFontSize || prev.eventFontSize, eventFontWeight: newSettings.eventFontWeight || prev.eventFontWeight @@ -867,6 +852,12 @@ export default function WeeklyView() { setLanguage(data.user.language || 'en'); if (data.user.startHour !== undefined) setStartHour(data.user.startHour); if (data.user.endHour !== undefined) setEndHour(data.user.endHour); + if (data.user.viewStyle !== undefined) { + setViewStyle(data.user.viewStyle as 'grid' | 'list'); + setShowTimeGrid(data.user.showTimeGrid ?? true); + } + if (data.user.viewDays !== undefined) setViewDays(data.user.viewDays); + if (data.user.cellDuration !== undefined) setCellDuration(data.user.cellDuration as CellDuration); setShowNextTask(data.user.showNextTask || false); setCalendarEditMode(data.user.calendarEditMode || false); if (data.user.fontSize) setFontSize(data.user.fontSize as 'S' | 'M' | 'L'); @@ -886,7 +877,6 @@ export default function WeeklyView() { if (data.user.bodyFont) setBodyFont(data.user.bodyFont); if (data.user.taskFontFamily) setTaskFontFamily(data.user.taskFontFamily); if (data.user.taskFontSize) setTaskFontSize(data.user.taskFontSize); - if (data.user.taskFontSize) setTaskFontSize(data.user.taskFontSize); if (data.user.taskFontWeight) setTaskFontWeight(data.user.taskFontWeight); if (data.user.eventFontFamily) setEventFontFamily(data.user.eventFontFamily); if (data.user.eventFontSize) setEventFontSize(data.user.eventFontSize); @@ -994,6 +984,11 @@ export default function WeeklyView() { // The old code had a default list. // Let's ensure we use the fetched lists. setSomedayLists(populatedLists); + + // Roll overdue tasks + if (dayTasks.length > 0) { + rollOverdueTasks(dayTasks); + } } } catch (error) { console.error('Error fetching data:', error); @@ -1002,6 +997,7 @@ export default function WeeklyView() { } } + // Get visible days based on current view setting const getVisibleDays = useCallback(() => { const days: Date[] = []; @@ -1140,6 +1136,110 @@ export default function WeeklyView() { return eventsByDay; }, [calendarEvents, currentWeekStart, viewDays]); + const rollOverdueTasks = useCallback(async (currentTasks: Task[]) => { + const autoRolling = profile.autoRolling ?? false; + if (!autoRolling) return; + + const now = new Date(); + const todayStr = formatDateToISO(now); + const today = new Date(todayStr); + + const overdue = currentTasks.filter(t => + !t.completed && + t.isRolling && + t.scheduledDate && + formatDateToISO(new Date(t.scheduledDate)) < todayStr + ); + + if (overdue.length === 0) return; + + console.log(`[ROLLING] Found ${overdue.length} overdue tasks to roll to today. autoRolling=${autoRolling}`); + + const updatedTasks = [...currentTasks]; + let hasChanges = false; + + const dailyEvents = getEventsForDate(today); + + for (const task of overdue) { + let targetSlot = task.startTime || '09:00'; // Default to 9am if no time + + // Collision detection + const isBlocked = (date: Date, slot: string, tasksToCheck: Task[]) => { + // Check other tasks in the updated list + const taskConflict = tasksToCheck.find(t => + t.id !== task.id && + t.scheduledDate && + formatDateToISO(new Date(t.scheduledDate)) === formatDateToISO(date) && + t.startTime === slot + ); + if (taskConflict) return true; + + // Check calendar events + const [h, m] = slot.split(':').map(Number); + const slotStart = new Date(date); + slotStart.setHours(h, m, 0, 0); + const slotEnd = new Date(slotStart); + slotEnd.setMinutes(slotEnd.getMinutes() + cellDuration); + + return dailyEvents.some(event => { + const eventStart = new Date(event.startTime); + const eventEnd = new Date(event.endTime); + return slotStart < eventEnd && slotEnd > eventStart; + }); + }; + + const findFreeSlot = (date: Date, preferred: string, tasksToCheck: Task[]) => { + let current = preferred; + let [h, m] = current.split(':').map(Number); + + while (isBlocked(date, current, tasksToCheck)) { + m += cellDuration; + if (m >= 60) { + h += 1; + m = 0; + } + if (h >= endHour) break; + current = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`; + } + return current; + }; + + const nextSlot = findFreeSlot(today, targetSlot, updatedTasks); + + // Update in DB + try { + const res = await fetch('/api/tasks', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + id: task.id, + scheduledDate: todayStr, + startTime: nextSlot + }) + }); + + if (res.ok) { + const data = await res.json(); + const taskIndex = updatedTasks.findIndex(t => t.id === task.id); + if (taskIndex !== -1) { + updatedTasks[taskIndex] = { + ...data.task, + createdAt: new Date(data.task.createdAt), + updatedAt: new Date(data.task.updatedAt) + }; + hasChanges = true; + } + } + } catch (err) { + console.error(`Failed to roll task ${task.id}:`, err); + } + } + + if (hasChanges) { + setTasks(updatedTasks.filter(t => t.dayOfWeek !== null && !t.somedayListId)); + } + }, [profile.autoRolling, cellDuration, endHour, getEventsForDate]); + // Check if a slot is protected by calendar events (only if slot starts within event time range) const isSlotProtected = useCallback((date: Date, slot: string): boolean => { if (!protectEventTimes) return false; @@ -1187,7 +1287,12 @@ export default function WeeklyView() { const goToNextWeek = () => navigate(new Date(currentWeekStart.getTime() + 7 * 24 * 60 * 60 * 1000), 'left', 'week'); const goToPrevDay = () => navigate(new Date(currentWeekStart.getTime() - 24 * 60 * 60 * 1000), 'right', 'day'); const goToNextDay = () => navigate(new Date(currentWeekStart.getTime() + 24 * 60 * 60 * 1000), 'left', 'day'); - const goToToday = () => setCurrentWeekStart(getStartOfWeek(new Date(), weekStartDay)); + const goToToday = () => { + const d = new Date(); + d.setHours(0, 0, 0, 0); + d.setDate(d.getDate() - 1); + setCurrentWeekStart(d); + }; // Task CRUD operations const addTask = async (date: Date, title: string, startTime?: string) => { @@ -1776,7 +1881,10 @@ export default function WeeklyView() { {[15, 30, 60].map(duration => ( + {/* Focus Mode Toggle */} - - {/* RIGHT SECTION: Navigation & Tools */} -
{/* Day/Night Mode Switch */} -
- ); - })} - {visibleSlots.map((slot) => { - const hour = getHourFromSlot(slot); - const minutes = slot.split(':')[1]; - const isHourStart = minutes === '00'; - const slotTasks = getTasksForSlot(date, slot); - const slotEvents = getEventsForSlot(date, slot); - const isActive = activeSlot?.day === date.getDay() && activeSlot?.slot === slot; - const isProtected = isSlotProtected(date, slot); - - const handleSlotClick = (e: React.MouseEvent) => { - if (isProtected) return; // Don't allow adding tasks to protected slots - - // Alt+Click to Create Calendar Event - if (e.altKey) { - e.stopPropagation(); - setCalendarEventModal({ - isOpen: true, - initialDate: date, - initialStartTime: slot - }); - return; - } - - if (!isActive) { - setActiveSlot({ day: date.getDay(), slot }); - setNewSlotTask(''); - } - }; - - const handleSlotSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - e.stopPropagation(); - const taskTitle = newSlotTask.trim(); - // Clear state immediately to prevent double submit - setActiveSlot(null); - setNewSlotTask(''); - if (taskTitle) { - await addTask(date, taskTitle, slot); - } - }; - - const handleSlotDrop = (e: React.DragEvent) => { - e.preventDefault(); - if (isProtected) return; // Don't allow dropping on protected slots - handleDrop(e, date.getDay(), slot); - }; - - const isDropTarget = dropPreview?.day === date.getDay() && dropPreview?.slot === slot; - - return ( -
!isProtected && handleDragOver(e, date.getDay(), slot)} - onDrop={handleSlotDrop} - > - {/* Drop preview indicator */} - {isDropTarget && !isProtected &&
} - {slotTasks.map(task => ( -
handleDragStart(e, task)} - onDragEnd={handleDragEnd} + - ))} -
- - ) : ( - { e.stopPropagation(); setEditingTaskId(task.id); }}> - {task.title} - - )} -
- {/* Edit button */} - - {/* Notes button */} - - {/* Roll button */} - {!task.completed && ( - - )} - {/* Recurrence button */} - - {/* Delete button */} - -
-
- ))} - {/* Calendar Events in time slot */} - {slotEvents.map(event => { - const eventHeight = getEventDuration(event); - const startTime = new Date(event.startTime); - const endTime = new Date(event.endTime); - const timeStr = `${startTime.getHours().toString().padStart(2, '0')}:${startTime.getMinutes().toString().padStart(2, '0')} - ${endTime.getHours().toString().padStart(2, '0')}:${endTime.getMinutes().toString().padStart(2, '0')}`; + {isUnlocked ? '🔓' : '🔒'} + +
+ ); + })} + {visibleSlots.map((slot) => { + const hour = getHourFromSlot(slot); + const minutes = slot.split(':')[1]; + const isHourStart = minutes === '00'; + const slotTasks = getTasksForSlot(date, slot); + const slotEvents = getEventsForSlot(date, slot); + const isActive = activeSlot?.day === date.getDay() && activeSlot?.slot === slot; + const isProtected = isSlotProtected(date, slot); - // Calculate offset within the slot based on event start time - const [slotHour, slotMinute] = slot.split(':').map(Number); - const slotStartMinutes = slotHour * 60 + slotMinute; - const eventStartMinutes = startTime.getHours() * 60 + startTime.getMinutes(); - const offsetMinutes = eventStartMinutes - slotStartMinutes; - const pixelsPerMinute = getSlotHeight(cellDuration) / cellDuration; - const topOffset = offsetMinutes * pixelsPerMinute; + const handleSlotClick = (e: React.MouseEvent) => { + if (isProtected) return; // Don't allow adding tasks to protected slots - // Convert hex to rgba for background, or use default - const eventColor = event.calendarColor || '#009a9a'; - const bgColor = eventColor.startsWith('#') - ? `${eventColor}20` // Add alpha for transparency - : eventColor; - const borderColor = eventColor.startsWith('#') - ? eventColor - : 'var(--weekly-teal)'; + // Alt+Click to Create Calendar Event + if (e.altKey) { + e.stopPropagation(); + setCalendarEventModal({ + isOpen: true, + initialDate: date, + initialStartTime: slot + }); + return; + } - return ( + if (!isActive) { + setActiveSlot({ day: date.getDay(), slot }); + setNewSlotTask(''); + } + }; + + const handleSlotSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + e.stopPropagation(); + const taskTitle = newSlotTask.trim(); + // Clear state immediately to prevent double submit + setActiveSlot(null); + setNewSlotTask(''); + if (taskTitle) { + await addTask(date, taskTitle, slot); + } + }; + + const handleSlotDrop = (e: React.DragEvent) => { + e.preventDefault(); + if (isProtected) return; // Don't allow dropping on protected slots + handleDrop(e, date.getDay(), slot); + }; + + const isDropTarget = dropPreview?.day === date.getDay() && dropPreview?.slot === slot; + + return ( +
!isProtected && handleDragOver(e, date.getDay(), slot)} + onDrop={handleSlotDrop} + > + {/* Drop preview indicator */} + {isDropTarget && !isProtected &&
} + {slotTasks.map(task => (
handleDragStart(e, task)} + onDragEnd={handleDragEnd} onClick={(e) => { e.stopPropagation(); - if (event.editable) { - setCalendarEventModal({ - isOpen: true, - event: event - }); + if (editingTaskId !== task.id) { + toggleTask(task.id); } }} > -
- 📅 - {event.title} + {editingTaskId === task.id ? ( +
{ + e.preventDefault(); + const input = e.currentTarget.elements.namedItem('title') as HTMLInputElement; + updateTask(task.id, input.value); + }} + onClick={(e) => e.stopPropagation()} + style={{ width: '100%', paddingRight: '20px' }} + > + updateTask(task.id, e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Escape') setEditingTaskId(null); + if (e.key === 'Enter') e.currentTarget.blur(); + }} + className="weekly-task-text" + style={{ width: '100%', background: 'transparent', border: 'none', borderBottom: '1px solid var(--weekly-border)', outline: 'none' }} + /> + {/* Duration Presets */} +
+ {[15, 30, 45, 60, 90, 120].map(m => ( + + ))} +
+
+ ) : ( + { e.stopPropagation(); setEditingTaskId(task.id); }}> + {task.title} + + )} +
+ {/* Edit button */} + + {/* Notes button */} + + {/* Roll button */} + {!task.completed && ( + + )} + {/* Recurrence button */} + + {/* Delete button */} +
-
{timeStr}
- ); - })} - {isActive && ( -
- setNewSlotTask(e.target.value)} - onBlur={async () => { - // Only save if still active (not already submitted) - if (activeSlot && newSlotTask.trim()) { - const taskTitle = newSlotTask.trim(); - setActiveSlot(null); - setNewSlotTask(''); - await addTask(date, taskTitle, slot); - } else { - setActiveSlot(null); - setNewSlotTask(''); - } - }} - onKeyDown={(e) => { - if (e.key === 'Escape') { - setActiveSlot(null); - setNewSlotTask(''); - } - }} - autoFocus - className="slot-input" - /> -
- )} -
- ); - })} - {/* All Day Events Section */} + ))} + {/* Calendar Events in time slot */} + {slotEvents.map(event => { + const eventHeight = getEventDuration(event); + const startTime = new Date(event.startTime); + const endTime = new Date(event.endTime); + const timeStr = `${startTime.getHours().toString().padStart(2, '0')}:${startTime.getMinutes().toString().padStart(2, '0')} - ${endTime.getHours().toString().padStart(2, '0')}:${endTime.getMinutes().toString().padStart(2, '0')}`; - {/* Untimed Tasks List below grid */} -
- {/* Filter for untimed tasks */} - {getTasksForDate(date) - .filter(task => !task.startTime) - .map(task => ( + // Calculate offset within the slot based on event start time + const [slotHour, slotMinute] = slot.split(':').map(Number); + const slotStartMinutes = slotHour * 60 + slotMinute; + const eventStartMinutes = startTime.getHours() * 60 + startTime.getMinutes(); + const offsetMinutes = eventStartMinutes - slotStartMinutes; + const pixelsPerMinute = getSlotHeight(cellDuration) / cellDuration; + const topOffset = offsetMinutes * pixelsPerMinute; + + // Convert hex to rgba for background, or use default + const eventColor = event.calendarColor || '#009a9a'; + const bgColor = eventColor.startsWith('#') + ? `${eventColor}20` // Add alpha for transparency + : eventColor; + const borderColor = eventColor.startsWith('#') + ? eventColor + : 'var(--weekly-teal)'; + + return ( +
{ + e.stopPropagation(); + if (event.editable) { + setCalendarEventModal({ + isOpen: true, + event: event + }); + } + }} + > +
+ 📅 + {event.title} +
+
{timeStr}
+
+ ); + })} + {isActive && ( +
+ setNewSlotTask(e.target.value)} + onBlur={async () => { + // Only save if still active (not already submitted) + if (activeSlot && newSlotTask.trim()) { + const taskTitle = newSlotTask.trim(); + setActiveSlot(null); + setNewSlotTask(''); + await addTask(date, taskTitle, slot); + } else { + setActiveSlot(null); + setNewSlotTask(''); + } + }} + onKeyDown={(e) => { + if (e.key === 'Escape') { + setActiveSlot(null); + setNewSlotTask(''); + } + }} + autoFocus + className="slot-input" + /> +
+ )} +
+ ); + })} + {/* All Day Events Section */} + + {/* Untimed Tasks List below grid */} +
+ {/* Filter for untimed tasks */} + {getTasksForDate(date) + .filter(task => !task.startTime) + .map(task => ( + toggleTask(task.id)} + onEdit={() => setEditingTaskId(task.id)} + onUpdate={(newTitle) => updateTask(task.id, newTitle)} + onDelete={() => deleteTask(task.id)} + onNotes={(notes) => updateTaskNotes(task.id, notes)} + onRollToggle={() => toggleTaskRolling(task.id)} + onRecurrence={() => setSelectedTaskForRecurrence(task)} + onDragStart={(e, t) => handleDragStart(e, t)} + onDragEnd={handleDragEnd} + variant="minimal" + /> + ))} +
+
+ ) : ( + <> + {/* Calendar Events */} + {getEventsForDate(date).map(event => { + const eventColor = event.calendarColor || '#009a9a'; + const bgColor = eventColor.startsWith('#') ? `${eventColor}20` : eventColor; + const borderColor = eventColor.startsWith('#') ? eventColor : 'var(--weekly-teal)'; + + return ( +
{ + e.stopPropagation(); + if (event.editable) { + setCalendarEventModal({ + isOpen: true, + event: event + }); + } + }} + style={{ + backgroundColor: bgColor, + borderLeftColor: borderColor, + color: borderColor, + cursor: event.editable ? 'pointer' : 'default' + }}> +
+ {new Date(event.startTime).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })} +
+
{event.title}
+
+ ); + })} + + {/* Tasks */} +
    + {getTasksForDate(date).map(task => ( setSelectedTaskForRecurrence(task)} onDragStart={(e, t) => handleDragStart(e, t)} onDragEnd={handleDragEnd} - variant="minimal" /> ))} -
- - ) : ( - <> - {/* Calendar Events */} - {getEventsForDate(date).map(event => { - const eventColor = event.calendarColor || '#009a9a'; - const bgColor = eventColor.startsWith('#') ? `${eventColor}20` : eventColor; - const borderColor = eventColor.startsWith('#') ? eventColor : 'var(--weekly-teal)'; + + + ) + } - return ( -
{ - e.stopPropagation(); - if (event.editable) { - setCalendarEventModal({ - isOpen: true, - event: event - }); - } - }} - style={{ - backgroundColor: bgColor, - borderLeftColor: borderColor, - color: borderColor, - cursor: event.editable ? 'pointer' : 'default' - }}> -
- {new Date(event.startTime).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })} -
-
{event.title}
-
- ); - })} - - {/* Tasks */} -
    - {getTasksForDate(date).map(task => ( - toggleTask(task.id)} - onEdit={() => setEditingTaskId(task.id)} - onUpdate={(newTitle) => updateTask(task.id, newTitle)} - onDelete={() => deleteTask(task.id)} - onNotes={(notes) => updateTaskNotes(task.id, notes)} - onRollToggle={() => toggleTaskRolling(task.id)} - onRecurrence={() => setSelectedTaskForRecurrence(task)} - onDragStart={(e, t) => handleDragStart(e, t)} - onDragEnd={handleDragEnd} - /> - ))} -
- - ) - } - - - ))} + + ); + })} @@ -2544,7 +2699,10 @@ export default function WeeklyView() { {somedayExpanded && ( -
+
{(somedayLists.length > 0 ? somedayLists : [{ id: 'default', title: 'LISTE', tasks: [] }]).slice(0, Math.max(somedayLists.length, viewDays)).map(list => (
{ - // Only drag if clicking the header + // Only drag if clicking the handle const target = e.target as HTMLElement; - if (!target.closest('.weekly-someday-list-title-header')) { + if (!target.closest('.someday-drag-handle')) { e.preventDefault(); return; } @@ -2656,7 +2814,10 @@ export default function WeeklyView() { } }} > -
+
+
+ +
{/* Editable Title */}