feat: Improve rolling tasks logic, UI polish, and fix profile settings persistence
- Relocated 'Now Do This' button and adjusted header alignment - Hid scrollbars in All-Day Events section for cleaner UI - Fixed 'Failed to update profile' error and ensured settings persistence - Improved rolling task logic with collision detection and auto-reschedule - Cleaned up duplicate code and resolved lint errors - Updated documentation (task.md, walkthrough.md, implementation_plan.md)
This commit is contained in:
parent
b97230dcb4
commit
72e62ead4a
@ -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")
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 => (
|
||||
<button
|
||||
key={duration}
|
||||
onClick={() => setCellDuration(duration as CellDuration)}
|
||||
onClick={() => {
|
||||
setCellDuration(duration as CellDuration);
|
||||
saveSetting('cellDuration', duration);
|
||||
}}
|
||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${cellDuration === duration ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||
>
|
||||
{duration}m
|
||||
@ -1791,7 +1899,10 @@ export default function WeeklyView() {
|
||||
{[1, 3, 5, 7].map(num => (
|
||||
<button
|
||||
key={num}
|
||||
onClick={() => setViewDays(num)}
|
||||
onClick={() => {
|
||||
setViewDays(num);
|
||||
saveSetting('viewDays', num);
|
||||
}}
|
||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${viewDays === num ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||
>
|
||||
{num}
|
||||
@ -1809,7 +1920,11 @@ export default function WeeklyView() {
|
||||
min="0"
|
||||
max={endHour - 1}
|
||||
value={startHour}
|
||||
onChange={(e) => setStartHour(Math.max(0, Math.min(parseInt(e.target.value) || 0, endHour - 1)))}
|
||||
onChange={(e) => {
|
||||
const val = Math.max(0, Math.min(parseInt(e.target.value) || 0, endHour - 1));
|
||||
setStartHour(val);
|
||||
saveSetting('startHour', val);
|
||||
}}
|
||||
className="w-10 p-0.5 border border-gray-200 dark:border-gray-700 rounded text-center bg-transparent focus:outline-none focus:border-teal-500"
|
||||
/>
|
||||
<span>-</span>
|
||||
@ -1818,7 +1933,11 @@ export default function WeeklyView() {
|
||||
min={startHour + 1}
|
||||
max="24"
|
||||
value={endHour}
|
||||
onChange={(e) => setEndHour(Math.max(startHour + 1, Math.min(parseInt(e.target.value) || 24, 24)))}
|
||||
onChange={(e) => {
|
||||
const val = Math.max(startHour + 1, Math.min(parseInt(e.target.value) || 24, 24));
|
||||
setEndHour(val);
|
||||
saveSetting('endHour', val);
|
||||
}}
|
||||
className="w-10 p-0.5 border border-gray-200 dark:border-gray-700 rounded text-center bg-transparent focus:outline-none focus:border-teal-500"
|
||||
/>
|
||||
</div>
|
||||
@ -1828,19 +1947,34 @@ export default function WeeklyView() {
|
||||
{/* View Style Toggles */}
|
||||
<div className="flex items-center gap-1 bg-gray-100 dark:bg-gray-800 rounded p-1" title="View Style">
|
||||
<button
|
||||
onClick={() => setShowTimeGrid(!showTimeGrid)}
|
||||
onClick={() => {
|
||||
const newVal = !showTimeGrid;
|
||||
setShowTimeGrid(newVal);
|
||||
if (newVal) setViewStyle('list'); // Default to list if grid is enabled
|
||||
saveSetting('showTimeGrid', newVal);
|
||||
}}
|
||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${showTimeGrid ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||
>
|
||||
Time Grid
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewStyle('grid')}
|
||||
onClick={() => {
|
||||
setViewStyle('grid');
|
||||
setShowTimeGrid(false);
|
||||
saveSetting('viewStyle', 'grid');
|
||||
saveSetting('showTimeGrid', false);
|
||||
}}
|
||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${!showTimeGrid && viewStyle === 'grid' ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||
>
|
||||
Grid
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewStyle('list')}
|
||||
onClick={() => {
|
||||
setViewStyle('list');
|
||||
if (!showTimeGrid) {
|
||||
saveSetting('viewStyle', 'list');
|
||||
}
|
||||
}}
|
||||
className={`px-2 py-0.5 text-xs rounded transition-colors ${!showTimeGrid && viewStyle === 'list' ? 'bg-white shadow-sm font-bold text-black' : 'text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700'}`}
|
||||
>
|
||||
List
|
||||
@ -1899,19 +2033,33 @@ export default function WeeklyView() {
|
||||
<span className="text-gray-300 mx-2">-</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* RIGHT SECTION: Navigation & Tools */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
const newVal = !showNextTask;
|
||||
setShowNextTask(newVal);
|
||||
saveSetting('showNextTask', newVal);
|
||||
}}
|
||||
className={`weekly-btn-secondary ${showNextTask ? 'active' : ''}`}
|
||||
title={showNextTask ? "Showing Next Task" : "Showing Motto"}
|
||||
>
|
||||
<Target size={14} className={showNextTask ? 'text-teal-500' : 'text-gray-400'} />
|
||||
Now DO THIS
|
||||
</button>
|
||||
|
||||
{/* Focus Mode Toggle */}
|
||||
<button
|
||||
onClick={() => setShowFocusMode(true)}
|
||||
className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600 transition-colors text-xs font-medium"
|
||||
className="weekly-btn-secondary"
|
||||
title="Enter Focus Mode"
|
||||
>
|
||||
<Target size={14} />
|
||||
<span>Focus Mode</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* RIGHT SECTION: Navigation & Tools */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Day/Night Mode Switch */}
|
||||
<button
|
||||
onClick={() => setDarkMode(!darkMode)}
|
||||
@ -2015,10 +2163,16 @@ export default function WeeklyView() {
|
||||
data-nav-type={viewDays > 1 ? 'week' : 'day'}
|
||||
style={{ viewTransitionName: viewDays > 1 ? 'week-grid' : 'none' } as React.CSSProperties}
|
||||
>
|
||||
{getVisibleDays().map((date, colIndex) => (
|
||||
{getVisibleDays().map((date, colIndex) => {
|
||||
const todayMidnight = new Date();
|
||||
todayMidnight.setHours(0, 0, 0, 0);
|
||||
const isToday = isSameDay(date, todayMidnight);
|
||||
const isPast = date < todayMidnight && !isToday;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={date.toISOString()}
|
||||
className={`weekly-day-column ${date.getDay() === 6 ? 'is-sat' : ''} ${date.getDay() === 0 ? 'is-sun' : ''} ${isSameDay(date, new Date()) ? 'is-today' : ''} ${date < new Date() && !isSameDay(date, new Date()) ? 'is-past' : ''}`}
|
||||
className={`weekly-day-column ${date.getDay() === 6 ? 'is-sat' : ''} ${date.getDay() === 0 ? 'is-sun' : ''} ${isToday ? 'is-today' : ''} ${isPast ? 'is-past' : ''}`}
|
||||
style={{ viewTransitionName: `day-${date.getFullYear()}-${date.getMonth()}-${date.getDate()}` } as any}
|
||||
>
|
||||
{/* Day Header */}
|
||||
@ -2303,8 +2457,8 @@ export default function WeeklyView() {
|
||||
minHeight: `15px`,
|
||||
position: 'absolute',
|
||||
top: `${topOffset}px`,
|
||||
left: '4px',
|
||||
right: '4px',
|
||||
left: '-10px',
|
||||
right: '-15px',
|
||||
zIndex: 5,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
@ -2445,8 +2599,9 @@ export default function WeeklyView() {
|
||||
)
|
||||
}
|
||||
|
||||
</div >
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</main >
|
||||
</div >
|
||||
|
||||
@ -2544,7 +2699,10 @@ export default function WeeklyView() {
|
||||
</div>
|
||||
|
||||
{somedayExpanded && (
|
||||
<div className={`weekly-someday-lists-grid cols-${Math.min(7, Math.max(1, viewDays))}`}>
|
||||
<div
|
||||
className={`weekly-someday-lists-grid cols-${Math.min(7, Math.max(1, viewDays))}`}
|
||||
onWheel={handleSomedayWheel}
|
||||
>
|
||||
{(somedayLists.length > 0 ? somedayLists : [{ id: 'default', title: 'LISTE', tasks: [] }]).slice(0, Math.max(somedayLists.length, viewDays)).map(list => (
|
||||
<div
|
||||
key={list.id}
|
||||
@ -2570,9 +2728,9 @@ export default function WeeklyView() {
|
||||
}}
|
||||
draggable
|
||||
onDragStart={(e) => {
|
||||
// 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() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="weekly-someday-list-title-header" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'grab' }} title="Drag to reorder">
|
||||
<div className="weekly-someday-list-title-header" style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||
<div className="someday-drag-handle" title="Drag to reorder">
|
||||
<GripVertical size={14} />
|
||||
</div>
|
||||
{/* Editable Title */}
|
||||
<input
|
||||
type="text"
|
||||
@ -2683,6 +2844,7 @@ export default function WeeklyView() {
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="someday-list-delete-btn"
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm('Delete this list?')) {
|
||||
@ -2694,7 +2856,7 @@ export default function WeeklyView() {
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{ border: 'none', background: 'none', cursor: 'pointer', fontSize: '1rem', color: '#ccc', marginLeft: 'auto' }}
|
||||
style={{ border: 'none', background: 'none', cursor: 'pointer', fontSize: '1rem', color: '#ccc', marginLeft: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
|
||||
title="Delete List"
|
||||
>
|
||||
×
|
||||
@ -2951,6 +3113,8 @@ export default function WeeklyView() {
|
||||
fontWeight={fontWeight}
|
||||
weekendColorSat={weekendColorSat}
|
||||
weekendColorSun={weekendColorSun}
|
||||
protectEventTimes={protectEventTimes}
|
||||
setProtectEventTimes={setProtectEventTimes}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -3550,6 +3714,8 @@ interface SettingsSidebarProps {
|
||||
fontWeight: string;
|
||||
weekendColorSat: string;
|
||||
weekendColorSun: string;
|
||||
protectEventTimes: boolean;
|
||||
setProtectEventTimes: (protect: boolean) => void;
|
||||
}
|
||||
|
||||
function SettingsSidebar({
|
||||
@ -3599,7 +3765,9 @@ function SettingsSidebar({
|
||||
eventFontWeight,
|
||||
fontWeight,
|
||||
weekendColorSat,
|
||||
weekendColorSun
|
||||
weekendColorSun,
|
||||
protectEventTimes,
|
||||
setProtectEventTimes
|
||||
}: SettingsSidebarProps) {
|
||||
const [activeTab, setActiveTab] = useState<'general' | 'calendar' | 'account'>('general');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@ -3902,7 +4070,12 @@ function SettingsSidebar({
|
||||
dateColor: profile.dateColor,
|
||||
taskColor: profile.taskColor,
|
||||
todayHighlightColor: profile.todayHighlightColor,
|
||||
});
|
||||
autoRolling: profile.autoRolling,
|
||||
protectEventTimes: profile.protectEventTimes || protectEventTimes,
|
||||
focusTimerDuration: profile.focusTimerDuration || focusTimerDuration,
|
||||
focusBreakDuration: profile.focusBreakDuration || focusBreakDuration,
|
||||
pastDayColor: profile.pastDayColor
|
||||
} as any);
|
||||
}
|
||||
|
||||
if (profile.focusTimerDuration && setFocusTimerDuration) {
|
||||
@ -4109,7 +4282,7 @@ function SettingsSidebar({
|
||||
min="0"
|
||||
max="23"
|
||||
value={profile.startHour}
|
||||
onChange={(e) => setProfile(prev => ({ ...prev, startHour: parseInt(e.target.value) }))}
|
||||
onChange={(e) => setProfile(prev => ({ ...prev, startHour: parseInt(e.target.value) || 0 }))}
|
||||
className="weekly-input"
|
||||
style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
|
||||
/>
|
||||
@ -4121,7 +4294,7 @@ function SettingsSidebar({
|
||||
min="1"
|
||||
max="24"
|
||||
value={profile.endHour}
|
||||
onChange={(e) => setProfile(prev => ({ ...prev, endHour: parseInt(e.target.value) }))}
|
||||
onChange={(e) => setProfile(prev => ({ ...prev, endHour: parseInt(e.target.value) || 0 }))}
|
||||
className="weekly-input"
|
||||
style={{ width: '100%', padding: '8px', border: '1px solid #ddd', borderRadius: '4px' }}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user