Skip to content

Commit 4ae2af9

Browse files
author
Raj Vishwakarma
committed
task-autosync: Implement task auto sync for dev environemnt
1 parent 68c212f commit 4ae2af9

File tree

10 files changed

+712
-83
lines changed

10 files changed

+712
-83
lines changed

frontend/package-lock.json

Lines changed: 432 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"@radix-ui/react-label": "^2.0.2",
1313
"@radix-ui/react-navigation-menu": "^1.1.4",
1414
"@radix-ui/react-select": "^2.1.0",
15+
"@radix-ui/react-slider": "^1.3.6",
1516
"@radix-ui/react-slot": "^1.0.2",
17+
"@radix-ui/react-switch": "^1.2.6",
1618
"acorn": "^8.11.2",
1719
"acorn-jsx": "^5.3.2",
1820
"acorn-walk": "^8.3.2",

frontend/src/Task-AutoSync.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useCallback, useEffect } from 'react';
2+
// We still don't need 'Props'
3+
import { syncTasksWithTwAndDb } from './components/HomeComponents/Tasks/Tasks';
4+
import { AutoSyncProps } from './components/utils/types';
5+
6+
export const useTaskAutoSync = (props: AutoSyncProps) => {
7+
const { isLoading, setIsLoading, isAutoSyncEnabled, syncInterval } = props;
8+
const handleSync = useCallback(async () => {
9+
if (isLoading) {
10+
console.log('Auto-sync: Sync already in progress, skipping.');
11+
return;
12+
}
13+
setIsLoading(true);
14+
try {
15+
await syncTasksWithTwAndDb();
16+
} catch (error) {
17+
console.error('Sync wrapper caught an error:', error);
18+
} finally {
19+
setIsLoading(false);
20+
}
21+
}, [isLoading, setIsLoading]);
22+
23+
useEffect(() => {
24+
let intervalId: NodeJS.Timeout | undefined = undefined;
25+
if (isAutoSyncEnabled) {
26+
intervalId = setInterval(() => {
27+
console.log('Auto-sync: Triggering periodic sync...');
28+
handleSync();
29+
}, syncInterval);
30+
}
31+
return () => {
32+
if (intervalId) {
33+
clearInterval(intervalId);
34+
}
35+
};
36+
}, [handleSync, isAutoSyncEnabled, syncInterval]);
37+
return { handleSync };
38+
};

frontend/src/components/HomeComponents/Navbar/NavbarDesktop.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
FileJson,
88
Terminal,
99
} from 'lucide-react';
10+
import { Switch } from '@/components/ui/switch';
11+
import { Slider } from '@/components/ui/slider';
1012
import {
1113
DropdownMenu,
1214
DropdownMenuContent,
@@ -36,6 +38,8 @@ import { url } from '@/components/utils/URLs';
3638
import { exportTasksAsJSON, exportTasksAsTXT } from '@/exports-tasks';
3739
import { useState } from 'react';
3840
import { DevLogs } from '../DevLogs/DevLogs';
41+
import { useTaskAutoSync } from '@/Task-AutoSync';
42+
import { Label } from '@/components/ui/label';
3943

4044
export const NavbarDesktop = (
4145
props: Props & {
@@ -45,6 +49,14 @@ export const NavbarDesktop = (
4549
) => {
4650
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
4751
const [isDevLogsOpen, setIsDevLogsOpen] = useState(false);
52+
const [autoSyncEnable, setAutoSyncEnable] = useState(false);
53+
const [syncInterval, setSyncInterval] = useState(1);
54+
useTaskAutoSync({
55+
isLoading: props.isLoading,
56+
setIsLoading: props.setIsLoading,
57+
isAutoSyncEnabled: autoSyncEnable,
58+
syncInterval: syncInterval * 60000,
59+
});
4860

4961
const handleExportJSON = () => {
5062
exportTasksAsJSON(props.tasks || []);
@@ -104,6 +116,33 @@ export const NavbarDesktop = (
104116
<span>Export tasks</span>
105117
</DropdownMenuItem>
106118
</DialogTrigger>
119+
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
120+
<div className="flex flex-col space-y-3 p-1 w-full">
121+
<div className="flex items-center justify-between space-x-2">
122+
<Label htmlFor="autosync-switch">Auto sync tasks</Label>
123+
<Switch
124+
id="autosync-switch"
125+
checked={autoSyncEnable}
126+
onCheckedChange={setAutoSyncEnable}
127+
/>
128+
</div>
129+
{autoSyncEnable && (
130+
<div className="flex flex-col space-y-2 pt-2">
131+
<Label htmlFor="sync-slider" className="text-sm">
132+
Sync every {syncInterval} minutes
133+
</Label>
134+
<Slider
135+
id="sync-slider"
136+
min={1}
137+
max={10}
138+
step={1}
139+
value={[syncInterval]}
140+
onValueChange={(value) => setSyncInterval(value[0])}
141+
/>
142+
</div>
143+
)}
144+
</div>
145+
</DropdownMenuItem>
107146
<DropdownMenuItem onClick={handleLogout}>
108147
<LogOut className="mr-2 h-4 w-4" />
109148
<span>Log out</span>

frontend/src/components/HomeComponents/Navbar/NavbarMobile.tsx

Lines changed: 137 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ import {
3737
import { Button } from '@/components/ui/button';
3838
import { exportTasksAsJSON, exportTasksAsTXT } from '@/exports-tasks';
3939
import { DevLogs } from '../DevLogs/DevLogs';
40+
import { useTaskAutoSync } from '@/Task-AutoSync';
41+
import { Label } from '@/components/ui/label';
42+
import { Switch } from '@/components/ui/switch';
43+
import { Slider } from '@/components/ui/slider';
4044

4145
export const NavbarMobile = (
4246
props: Props & { setIsOpen: (isOpen: boolean) => void; isOpen: boolean } & {
@@ -45,7 +49,16 @@ export const NavbarMobile = (
4549
}
4650
) => {
4751
const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);
52+
const [isAutoSyncDialogOpen, setIsAutoSyncDialogOpen] = useState(false);
4853
const [isDevLogsOpen, setIsDevLogsOpen] = useState(false);
54+
const [autoSyncEnable, setAutoSyncEnable] = useState(false);
55+
const [syncInterval, setSyncInterval] = useState(1);
56+
useTaskAutoSync({
57+
isLoading: props.isLoading,
58+
setIsLoading: props.setIsLoading,
59+
isAutoSyncEnabled: autoSyncEnable,
60+
syncInterval: syncInterval * 60000,
61+
});
4962

5063
const handleExportJSON = () => {
5164
exportTasksAsJSON(props.tasks || []);
@@ -76,34 +89,35 @@ export const NavbarMobile = (
7689
<SheetHeader>
7790
<SheetTitle className="font-bold text-xl">CCSync</SheetTitle>
7891
</SheetHeader>
79-
<Dialog
80-
open={isExportDialogOpen}
81-
onOpenChange={setIsExportDialogOpen}
82-
>
83-
<nav className="flex flex-col justify-center items-center gap-2 mt-4">
84-
{routeList.map(({ href, label }: RouteProps) => (
85-
<a
86-
rel="noreferrer noopener"
87-
key={label}
88-
href={href}
89-
onClick={() => props.setIsOpen(false)}
90-
className={buttonVariants({ variant: 'ghost' })}
91-
>
92-
{label}
93-
</a>
94-
))}
92+
93+
<nav className="flex flex-col justify-center items-center gap-2 mt-4">
94+
{routeList.map(({ href, label }: RouteProps) => (
9595
<a
9696
rel="noreferrer noopener"
97-
href={url.githubRepoURL}
98-
target="_blank"
99-
className={`w-[130px] border ${buttonVariants({
100-
variant: 'secondary',
101-
})}`}
97+
key={label}
98+
href={href}
99+
onClick={() => props.setIsOpen(false)}
100+
className={buttonVariants({ variant: 'ghost' })}
102101
>
103-
<Github className="mr-2 w-5 h-5" />
104-
Github
102+
{label}
105103
</a>
104+
))}
105+
<a
106+
rel="noreferrer noopener"
107+
href={url.githubRepoURL}
108+
target="_blank"
109+
className={`w-[130px] border ${buttonVariants({
110+
variant: 'secondary',
111+
})}`}
112+
>
113+
<Github className="mr-2 w-5 h-5" />
114+
Github
115+
</a>
106116

117+
<Dialog
118+
open={isExportDialogOpen}
119+
onOpenChange={setIsExportDialogOpen}
120+
>
107121
<DialogTrigger asChild>
108122
<div
109123
className={`w-[130px] cursor-pointer border ${buttonVariants({
@@ -114,63 +128,107 @@ export const NavbarMobile = (
114128
Export Tasks
115129
</div>
116130
</DialogTrigger>
117-
<div
118-
onClick={() => {
119-
setIsDevLogsOpen(true);
120-
props.setIsOpen(false);
121-
}}
122-
className={`w-[130px] cursor-pointer border ${buttonVariants({
123-
variant: 'secondary',
124-
})}`}
125-
>
126-
<Terminal className="mr-2 w-5 h-5" />
127-
Developer Logs
128-
</div>
129-
<div
130-
onClick={() => deleteAllTasks(props)}
131-
className={`w-[130px] border ${buttonVariants({
132-
variant: 'destructive',
133-
})}`}
134-
>
135-
<Trash2 className="mr-2 w-5 h-5" />
136-
Delete All Tasks
137-
</div>
138-
<div
139-
onClick={handleLogout}
140-
className={`w-[130px] border ${buttonVariants({
141-
variant: 'destructive',
142-
})}`}
143-
>
144-
<LogOut className="mr-2 w-5 h-5" />
145-
Log out
146-
</div>
147-
</nav>
148-
<DialogContent>
149-
<DialogHeader>
150-
<DialogTitle>Choose Export Format</DialogTitle>
151-
<DialogDescription>
152-
Would you like to download your tasks as a JSON file or a TXT
153-
file?
154-
</DialogDescription>
155-
</DialogHeader>
156-
<div className="flex flex-col sm:flex-row justify-end gap-2 mt-4">
157-
<Button
158-
onClick={handleExportTXT}
159-
className="w-full sm:w-auto hover:bg-white bg-[#3B82F6]"
160-
>
161-
<FileText className="mr-2 h-4 w-4" />
162-
Download .txt
163-
</Button>
164-
<Button
165-
onClick={handleExportJSON}
166-
className="w-full sm:w-auto hover:bg-white bg-[#3B82F6]"
131+
<DialogContent>
132+
<DialogHeader>
133+
<DialogTitle>Choose Export Format</DialogTitle>
134+
<DialogDescription>
135+
Would you like to download your tasks as a JSON file or a
136+
TXT file?
137+
</DialogDescription>
138+
</DialogHeader>
139+
<div className="flex flex-col sm:flex-row justify-end gap-2 mt-4">
140+
<Button
141+
onClick={handleExportTXT}
142+
className="w-full sm:w-auto hover:bg-white bg-[#3B82F6]"
143+
>
144+
<FileText className="mr-2 h-4 w-4" />
145+
Download .txt
146+
</Button>
147+
<Button
148+
onClick={handleExportJSON}
149+
className="w-full sm:w-auto hover:bg-white bg-[#3B82F6]"
150+
>
151+
<FileJson className="mr-2 h-4 w-4" />
152+
Download .json
153+
</Button>
154+
</div>
155+
</DialogContent>
156+
</Dialog>
157+
<Dialog
158+
open={isAutoSyncDialogOpen}
159+
onOpenChange={setIsAutoSyncDialogOpen}
160+
>
161+
<DialogTrigger asChild>
162+
<div
163+
className={`w-[130px] cursor-pointer border ${buttonVariants({
164+
variant: 'secondary',
165+
})}`}
167166
>
168-
<FileJson className="mr-2 h-4 w-4" />
169-
Download .json
170-
</Button>
171-
</div>
172-
</DialogContent>
173-
</Dialog>
167+
Auto-sync
168+
</div>
169+
</DialogTrigger>
170+
<DialogContent>
171+
<div className="flex flex-col space-y-4 pt-2">
172+
<div className="flex mt-2 items-center justify-between space-x-2">
173+
<Label htmlFor="autosync-switch" className="text-base">
174+
Enable Auto-Sync
175+
</Label>
176+
<Switch
177+
id="autosync-switch"
178+
checked={autoSyncEnable}
179+
onCheckedChange={setAutoSyncEnable}
180+
/>
181+
</div>
182+
183+
{autoSyncEnable && (
184+
<div className="flex flex-col space-y-3 pt-2">
185+
<Label htmlFor="sync-slider" className="text-sm">
186+
Sync every {syncInterval} minutes
187+
</Label>
188+
<Slider
189+
id="sync-slider"
190+
min={1}
191+
max={10}
192+
step={1}
193+
value={[syncInterval]}
194+
onValueChange={(value) => setSyncInterval(value[0])}
195+
/>
196+
</div>
197+
)}
198+
</div>
199+
</DialogContent>
200+
</Dialog>
201+
<div
202+
onClick={() => {
203+
setIsDevLogsOpen(true);
204+
props.setIsOpen(false);
205+
}}
206+
className={`w-[130px] cursor-pointer border ${buttonVariants({
207+
variant: 'secondary',
208+
})}`}
209+
>
210+
<Terminal className="mr-2 w-5 h-5" />
211+
Developer Logs
212+
</div>
213+
<div
214+
onClick={() => deleteAllTasks(props)}
215+
className={`w-[130px] border ${buttonVariants({
216+
variant: 'destructive',
217+
})}`}
218+
>
219+
<Trash2 className="mr-2 w-5 h-5" />
220+
Delete All Tasks
221+
</div>
222+
<div
223+
onClick={handleLogout}
224+
className={`w-[130px] border ${buttonVariants({
225+
variant: 'destructive',
226+
})}`}
227+
>
228+
<LogOut className="mr-2 w-5 h-5" />
229+
Log out
230+
</div>
231+
</nav>
174232
</SheetContent>
175233
</Sheet>
176234
<DevLogs isOpen={isDevLogsOpen} onOpenChange={setIsDevLogsOpen} />

frontend/src/components/HomeComponents/Tasks/Tasks.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useState } from 'react';
1+
import { useEffect, useState, useCallback } from 'react';
22
import { Task } from '../../utils/types';
33
import { ReportsView } from './ReportsView';
44
import {
@@ -70,6 +70,7 @@ import {
7070
import { debounce } from '@/components/utils/utils';
7171

7272
const db = new TasksDatabase();
73+
export let syncTasksWithTwAndDb: () => any;
7374

7475
export const Tasks = (
7576
props: Props & {
@@ -204,7 +205,7 @@ export const Tasks = (
204205
fetchTasksForEmail();
205206
}, [props.email]);
206207

207-
async function syncTasksWithTwAndDb() {
208+
syncTasksWithTwAndDb = useCallback(async () => {
208209
try {
209210
const { email: user_email, encryptionSecret, UUID } = props;
210211
const taskwarriorTasks = await fetchTaskwarriorTasks({
@@ -241,7 +242,7 @@ export const Tasks = (
241242
console.error('Error syncing tasks:', error);
242243
toast.error(`Failed to sync tasks. Please try again.`);
243244
}
244-
}
245+
}, [props.email, props.encryptionSecret, props.UUID]); // Add dependencies
245246

246247
async function handleAddTask(
247248
email: string,

0 commit comments

Comments
 (0)