diff --git a/frontend/src/components/HomeComponents/Navbar/NavbarDesktop.tsx b/frontend/src/components/HomeComponents/Navbar/NavbarDesktop.tsx index a4d3a8c7..5d145544 100644 --- a/frontend/src/components/HomeComponents/Navbar/NavbarDesktop.tsx +++ b/frontend/src/components/HomeComponents/Navbar/NavbarDesktop.tsx @@ -42,10 +42,11 @@ import { exportTasksAsJSON, exportTasksAsTXT, } from '@/components/utils/ExportTasks'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { DevLogs } from '@/components/HomeComponents/DevLogs/DevLogs'; import { useTaskAutoSync } from '@/components/utils/TaskAutoSync'; import { Label } from '@/components/ui/label'; +import { hashKey } from '../Tasks/tasks-utils'; export const NavbarDesktop = ( props: Props & { @@ -58,6 +59,8 @@ export const NavbarDesktop = ( const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false); const [autoSyncEnable, setAutoSyncEnable] = useState(false); const [syncInterval, setSyncInterval] = useState(1); + const [autoSyncOnEdit, setAutoSyncOnEdit] = useState(true); + useTaskAutoSync({ isLoading: props.isLoading, setIsLoading: props.setIsLoading, @@ -65,6 +68,26 @@ export const NavbarDesktop = ( syncInterval: syncInterval * 60000, }); + // Load setting from localStorage + useEffect(() => { + const hashedKey = hashKey('autoSyncOnEdit', props.email); + const stored = localStorage.getItem(hashedKey); + if (stored !== null) { + setAutoSyncOnEdit(stored === 'true'); + } else { + localStorage.setItem(hashedKey, 'true'); + setAutoSyncOnEdit(true); + } + }, [props.email]); + + // Save setting and notify Tasks component + const handleAutoSyncOnEditChange = (checked: boolean) => { + setAutoSyncOnEdit(checked); + const hashedKey = hashKey('autoSyncOnEdit', props.email); + localStorage.setItem(hashedKey, checked.toString()); + window.dispatchEvent(new Event('autoSyncOnEditChanged')); + }; + const handleExportJSON = () => { exportTasksAsJSON(props.tasks || []); setIsExportDialogOpen(false); @@ -234,6 +257,18 @@ export const NavbarDesktop = ( )} + e.preventDefault()}> +
+ + +
+
Log out diff --git a/frontend/src/components/HomeComponents/Navbar/NavbarMobile.tsx b/frontend/src/components/HomeComponents/Navbar/NavbarMobile.tsx index ea803250..491a3b40 100644 --- a/frontend/src/components/HomeComponents/Navbar/NavbarMobile.tsx +++ b/frontend/src/components/HomeComponents/Navbar/NavbarMobile.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Sheet, SheetContent, @@ -45,6 +45,7 @@ import { useTaskAutoSync } from '@/components/utils/TaskAutoSync'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Slider } from '@/components/ui/slider'; +import { hashKey } from '../Tasks/tasks-utils'; export const NavbarMobile = ( props: Props & { setIsOpen: (isOpen: boolean) => void; isOpen: boolean } & { @@ -58,6 +59,8 @@ export const NavbarMobile = ( const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false); const [autoSyncEnable, setAutoSyncEnable] = useState(false); const [syncInterval, setSyncInterval] = useState(1); + const [autoSyncOnEdit, setAutoSyncOnEdit] = useState(true); + useTaskAutoSync({ isLoading: props.isLoading, setIsLoading: props.setIsLoading, @@ -65,6 +68,26 @@ export const NavbarMobile = ( syncInterval: syncInterval * 60000, }); + // Load setting from localStorage + useEffect(() => { + const hashedKey = hashKey('autoSyncOnEdit', props.email); + const stored = localStorage.getItem(hashedKey); + if (stored !== null) { + setAutoSyncOnEdit(stored === 'true'); + } else { + localStorage.setItem(hashedKey, 'true'); + setAutoSyncOnEdit(true); + } + }, [props.email]); + + // Save setting and notify Tasks component + const handleAutoSyncOnEditChange = (checked: boolean) => { + setAutoSyncOnEdit(checked); + const hashedKey = hashKey('autoSyncOnEdit', props.email); + localStorage.setItem(hashedKey, checked.toString()); + window.dispatchEvent(new Event('autoSyncOnEditChanged')); + }; + const handleExportJSON = () => { exportTasksAsJSON(props.tasks || []); setIsExportDialogOpen(false); @@ -215,6 +238,20 @@ export const NavbarMobile = ( /> )} + +
+ + +
diff --git a/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx b/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx index 5f90174c..01852cac 100644 --- a/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx +++ b/frontend/src/components/HomeComponents/Navbar/__tests__/NavbarDesktop.test.tsx @@ -154,7 +154,10 @@ describe('NavbarDesktop', () => { render(); await userEvent.click(screen.getByText('CN')); - await userEvent.click(screen.getByText('toggle')); + + // Click the first toggle (Auto sync tasks) + const toggleButtons = screen.getAllByText('toggle'); + await userEvent.click(toggleButtons[0]); expect(screen.getByTestId('sync-slider')).toBeInTheDocument(); }); diff --git a/frontend/src/components/HomeComponents/Navbar/__tests__/__snapshots__/NavbarDesktop.test.tsx.snap b/frontend/src/components/HomeComponents/Navbar/__tests__/__snapshots__/NavbarDesktop.test.tsx.snap index 4a8e3e30..5e87c5c0 100644 --- a/frontend/src/components/HomeComponents/Navbar/__tests__/__snapshots__/NavbarDesktop.test.tsx.snap +++ b/frontend/src/components/HomeComponents/Navbar/__tests__/__snapshots__/NavbarDesktop.test.tsx.snap @@ -374,6 +374,21 @@ exports[`NavbarDesktop renders consistently (snapshot) 1`] = ` +
+
+ + +
+
>( new Set() ); + const [autoSyncOnEdit, setAutoSyncOnEdit] = useState(true); const tableRef = useRef(null); const [hotkeysEnabled, setHotkeysEnabled] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); @@ -212,6 +213,30 @@ export const Tasks = ( setPinnedTasks(getPinnedTasks(props.email)); }, [props.email]); + // Load setting and listen for changes from navbar + useEffect(() => { + const hashedKey = hashKey('autoSyncOnEdit', props.email); + const stored = localStorage.getItem(hashedKey); + if (stored !== null) { + setAutoSyncOnEdit(stored === 'true'); + } else { + localStorage.setItem(hashedKey, 'true'); + setAutoSyncOnEdit(true); + } + + const handleStorageChange = () => { + const updated = localStorage.getItem(hashedKey); + if (updated !== null) { + setAutoSyncOnEdit(updated === 'true'); + } + }; + + window.addEventListener('autoSyncOnEditChanged', handleStorageChange); + return () => { + window.removeEventListener('autoSyncOnEditChanged', handleStorageChange); + }; + }, [props.email]); + useEffect(() => { const fetchTasksForEmail = async () => { try { @@ -432,6 +457,12 @@ export const Tasks = ( annotations, }); + // Auto-sync after edit if enabled (on by default) + if (autoSyncOnEdit) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + await syncTasksWithTwAndDb(); + } + setIsAddTaskOpen(false); } catch (error) { console.error('Failed to edit task:', error); diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx index d650a64b..47a009b2 100644 --- a/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx +++ b/frontend/src/components/HomeComponents/Tasks/__tests__/Tasks.test.tsx @@ -37,7 +37,7 @@ jest.mock('../tasks-utils', () => { getTimeSinceLastSync: jest .fn() .mockReturnValue('Last updated 5 minutes ago'), - hashKey: jest.fn().mockReturnValue('mockHashedKey'), + hashKey: jest.fn((key: string) => `mockHashedKey-${key}`), getPinnedTasks: jest.fn().mockReturnValue(new Set()), togglePinnedTask: jest.fn(), }; @@ -203,6 +203,8 @@ describe('Tasks Component', () => { beforeEach(() => { localStorageMock.clear(); jest.clearAllMocks(); + // Disable auto-sync on edit for tests (to avoid unexpected sync calls) + localStorageMock.setItem('mockHashedKey-autoSyncOnEdit', 'false'); }); describe('Rendering & Basic UI', () => { @@ -255,7 +257,7 @@ describe('Tasks Component', () => { describe('LocalStorage', () => { test('loads "tasksPerPage" from localStorage on initial render', async () => { - localStorageMock.setItem('mockHashedKey', '20'); + localStorageMock.setItem('mockHashedKey-tasksPerPage', '20'); render(); @@ -277,7 +279,7 @@ describe('Tasks Component', () => { expect(screen.getByTestId('total-pages')).toHaveTextContent('4'); expect(localStorageMock.setItem).toHaveBeenCalledWith( - 'mockHashedKey', + 'mockHashedKey-tasksPerPage', '5' );