diff --git a/app/bun.lockb b/app/bun.lockb index 6b315617..42e4f6ce 100755 Binary files a/app/bun.lockb and b/app/bun.lockb differ diff --git a/app/eslint.config.mjs b/app/eslint.config.mjs index 833aa7b0..172b4173 100644 --- a/app/eslint.config.mjs +++ b/app/eslint.config.mjs @@ -57,7 +57,6 @@ export default [ allowConstantExport: true, }, ], - "@typescript-eslint/no-empty-object-type": "off", }, }, diff --git a/app/package.json b/app/package.json index 847aee45..d007b921 100644 --- a/app/package.json +++ b/app/package.json @@ -44,9 +44,9 @@ "jotai": "^2.11.0", "ky": "^1.7.4", "lucide-react": "^0.469.0", - "react": "^19.0.0", + "react": "^19.2.0", "react-day-picker": "8.10.1", - "react-dom": "^19.0.0", + "react-dom": "^19.2.0", "react-hook-form": "^7.54.2", "react-markdown": "^9.0.1", "react-resizable-panels": "^2.1.7", @@ -65,8 +65,8 @@ "@tailwindcss/typography": "^0.5.15", "@tanstack/eslint-plugin-router": "^1.92.7", "@types/node": "^22.10.5", - "@types/react": "^19.0.2", - "@types/react-dom": "^19.0.2", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", "@typescript-eslint/eslint-plugin": "^8.19.0", "@typescript-eslint/parser": "^8.19.0", "@vitejs/plugin-react": "^4.3.4", @@ -74,7 +74,7 @@ "babel-plugin-react-compiler": "^19.0.0-beta-55955c9-20241229", "eslint": "^9.17.0", "eslint-plugin-react-compiler": "^19.0.0-beta-55955c9-20241229", - "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", "install": "^0.13.0", diff --git a/app/src/components/notifications-popover/notification-settings-dropdown.tsx b/app/src/components/notifications-popover/notification-settings-dropdown.tsx index a1bb9551..0432f918 100644 --- a/app/src/components/notifications-popover/notification-settings-dropdown.tsx +++ b/app/src/components/notifications-popover/notification-settings-dropdown.tsx @@ -95,6 +95,7 @@ export function NotificationSettingsDropdown() { }; useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect setBrowserPermission(hasPushPermission()); const permissionChangeHandler = () => { diff --git a/app/src/components/timer-edit-dialog.tsx b/app/src/components/timer-edit-dialog.tsx index 82832a24..de5ddbc9 100644 --- a/app/src/components/timer-edit-dialog.tsx +++ b/app/src/components/timer-edit-dialog.tsx @@ -25,16 +25,14 @@ export const TimerEditDialog = (props: { timer: TimerResponse; }) => { const [projectId, setProjectId] = React.useState( - props.timer?.projectId ?? undefined, + props.timer.projectId ?? undefined, ); const [activityName, setActivityName] = React.useState( - props.timer?.activityName ?? undefined, - ); - const [note, setNote] = React.useState( - props.timer?.note ?? undefined, + props.timer.activityName ?? undefined, ); + const [note, setNote] = React.useState(props.timer.note); const [startTimeISO, setStartTimeISO] = React.useState( - props.timer?.startTime, + props.timer.startTime, ); const { projects, activities } = useTimeTrackingData({ @@ -51,38 +49,39 @@ export const TimerEditDialog = (props: { [activities, activityName], ); - const { mutate: updateTimerMutate } = - timeTrackingMutations.useEditTimer({ - onSuccess: () => { - props.onOpenChange(false); - }, - }); + const closeDialog = () => { + props.onOpenChange(false); + }; + + const { mutate: updateTimerMutate } = timeTrackingMutations.useEditTimer({ + onSuccess: closeDialog, + }); const updateTimer = () => { - updateTimerMutate( - { - projectId: projectId, - projectName: selectedProject?.projectName ?? "", - activityId: selectedActivity?.activity ?? "", - activityName: activityName, - userNote: note ?? "", - startTime: startTimeISO, - }, - { - onSuccess: () => { - props.onOpenChange(false); - }, - }, - ); + updateTimerMutate({ + projectId: projectId, + projectName: selectedProject?.projectName ?? "", + activityId: selectedActivity?.activity ?? "", + activityName: activityName, + userNote: note ?? "", + startTime: startTimeISO, + }); }; + const hydrateDraftFromCurrentTimer = React.useEffectEvent(() => { + setProjectId(props.timer.projectId ?? undefined); + setActivityName(props.timer.activityName ?? undefined); + setNote(props.timer.note); + setStartTimeISO(props.timer.startTime); + }); + React.useEffect(() => { - // Synchronize state with props.timer when it changes - setProjectId(props.timer?.projectId ?? undefined); - setActivityName(props.timer?.activityName ?? undefined); - setNote(props.timer?.note ?? undefined); // Convert null to undefined for consistency - setStartTimeISO(props.timer?.startTime); - }, [props.timer]); + // Initialize draft state only when dialog opens. + // While open, keep local edits as source of truth even if timer query refetches. + if (props.open) { + hydrateDraftFromCurrentTimer(); + } + }, [props.open]); const activitiesRef = React.useRef(null); const noteInputRef = React.useRef(null); @@ -93,13 +92,15 @@ export const TimerEditDialog = (props: { // Handle time input change from "HH:mm" const handleTimeInputChange = (newTimeValue: string) => { - if (!props.timer?.startTime) { + const baseStartTime = startTimeISO ?? props.timer.startTime; + + if (!baseStartTime) { console.warn( "Original timer start time not available to derive date for time input change.", ); return; } - const originalTimerDate = dayjs(props.timer.startTime); // Base date from original timer + const originalTimerDate = dayjs(baseStartTime); const [hours, minutes] = newTimeValue.split(":").map(Number); let newFullDateTime = originalTimerDate @@ -121,12 +122,7 @@ export const TimerEditDialog = (props: { if (!props.open || !projects) return null; return ( - { - props.onOpenChange(open); - }} - > +
({ // eslint-disable-next-line react-compiler/react-compiler "use no memo"; + // eslint-disable-next-line react-hooks/incompatible-library const table = useReactTable({ data, columns, diff --git a/app/src/routes/_layout/time-tracking/-components/timeline-view.tsx b/app/src/routes/_layout/time-tracking/-components/timeline-view.tsx index 4a2743b1..d9f10a89 100644 --- a/app/src/routes/_layout/time-tracking/-components/timeline-view.tsx +++ b/app/src/routes/_layout/time-tracking/-components/timeline-view.tsx @@ -625,6 +625,7 @@ export function TimelineView({ timeEntries, dateRange }: TimelineViewProps) { const to = parseISO(dateRange.to); const today = startOfDay(new Date()); const target = today >= from && today <= to ? today : from; + // eslint-disable-next-line react-hooks/set-state-in-effect setCurrentWeekStart(startOfWeek(target, { weekStartsOn: 1 })); setSelectedDate(target); }, [dateRange.from, dateRange.to]);