diff --git a/backend/controllers/edit_task.go b/backend/controllers/edit_task.go index c7ca747d..8ef0e5b6 100644 --- a/backend/controllers/edit_task.go +++ b/backend/controllers/edit_task.go @@ -52,6 +52,7 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) { end := requestBody.End depends := requestBody.Depends due := requestBody.Due + recur := requestBody.Recur if taskID == "" { http.Error(w, "taskID is required", http.StatusBadRequest) @@ -63,7 +64,7 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) { Name: "Edit Task", Execute: func() error { logStore.AddLog("INFO", fmt.Sprintf("Editing task ID: %s", taskID), uuid, "Edit Task") - err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID, tags, project, start, entry, wait, end, depends, due) + err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID, tags, project, start, entry, wait, end, depends, due, recur) if err != nil { logStore.AddLog("ERROR", fmt.Sprintf("Failed to edit task ID %s: %v", taskID, err), uuid, "Edit Task") return err diff --git a/backend/models/request_body.go b/backend/models/request_body.go index 8e1d97ea..7f281c12 100644 --- a/backend/models/request_body.go +++ b/backend/models/request_body.go @@ -37,6 +37,7 @@ type EditTaskRequestBody struct { End string `json:"end"` Depends []string `json:"depends"` Due string `json:"due"` + Recur string `json:"recur"` } type CompleteTaskRequestBody struct { Email string `json:"email"` diff --git a/backend/utils/tw/edit_task.go b/backend/utils/tw/edit_task.go index fb7667f9..bc287aec 100644 --- a/backend/utils/tw/edit_task.go +++ b/backend/utils/tw/edit_task.go @@ -7,7 +7,7 @@ import ( "strings" ) -func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID string, tags []string, project string, start string, entry string, wait string, end string, depends []string, due string) error { +func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID string, tags []string, project string, start string, entry string, wait string, end string, depends []string, due string, recur string) error { if err := utils.ExecCommand("rm", "-rf", "/root/.task"); err != nil { return fmt.Errorf("error deleting Taskwarrior data: %v", err) } @@ -112,6 +112,13 @@ func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID st } } + // Handle recur - this will automatically set rtype field + if recur != "" { + if err := utils.ExecCommand("task", taskID, "modify", "recur:"+recur); err != nil { + return fmt.Errorf("failed to set recur %s: %v", recur, err) + } + } + // Sync Taskwarrior again if err := SyncTaskwarrior(tempDir); err != nil { return err diff --git a/backend/utils/tw/taskwarrior_test.go b/backend/utils/tw/taskwarrior_test.go index c8be55fb..cf35a627 100644 --- a/backend/utils/tw/taskwarrior_test.go +++ b/backend/utils/tw/taskwarrior_test.go @@ -23,7 +23,7 @@ func TestSyncTaskwarrior(t *testing.T) { } func TestEditTaskInATaskwarrior(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", nil, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z") + err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", nil, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "weekly") if err != nil { t.Errorf("EditTaskInTaskwarrior() failed: %v", err) } else { @@ -68,7 +68,7 @@ func TestAddTaskWithTags(t *testing.T) { } func TestEditTaskWithTagAddition(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "+important"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z") + err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "+important"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "daily") if err != nil { t.Errorf("EditTaskInTaskwarrior with tag addition failed: %v", err) } else { @@ -77,7 +77,7 @@ func TestEditTaskWithTagAddition(t *testing.T) { } func TestEditTaskWithTagRemoval(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"-work", "-lowpriority"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z") + err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"-work", "-lowpriority"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "monthly") if err != nil { t.Errorf("EditTaskInTaskwarrior with tag removal failed: %v", err) } else { @@ -86,7 +86,7 @@ func TestEditTaskWithTagRemoval(t *testing.T) { } func TestEditTaskWithMixedTagOperations(t *testing.T) { - err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "-work", "normal"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z") + err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "-work", "normal"}, "project", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-29T18:30:00.000Z", "2025-11-30T18:30:00.000Z", nil, "2025-12-01T18:30:00.000Z", "yearly") if err != nil { t.Errorf("EditTaskInTaskwarrior with mixed tag operations failed: %v", err) } else { diff --git a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx index d5b0df33..1ecb4fcd 100644 --- a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx +++ b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx @@ -136,6 +136,9 @@ export const Tasks = ( const [editedDepends, setEditedDepends] = useState([]); const [dependsDropdownOpen, setDependsDropdownOpen] = useState(false); const [dependsSearchTerm, setDependsSearchTerm] = useState(''); + const [isEditingRecur, setIsEditingRecur] = useState(false); + const [editedRecur, setEditedRecur] = useState(''); + const [originalRecur, setOriginalRecur] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [debouncedTerm, setDebouncedTerm] = useState(''); const [lastSyncTime, setLastSyncTime] = useState(null); @@ -379,7 +382,8 @@ export const Tasks = ( wait: string, end: string, depends: string[], - due: string + due: string, + recur: string ) { try { await editTaskOnBackend({ @@ -397,6 +401,7 @@ export const Tasks = ( end, depends, due, + recur, }); console.log('Task edited successfully!'); @@ -444,7 +449,8 @@ export const Tasks = ( task.wait || '', task.end || '', task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditing(false); }; @@ -464,7 +470,8 @@ export const Tasks = ( task.wait || '', task.end || '', task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingProject(false); }; @@ -485,7 +492,8 @@ export const Tasks = ( task.wait, task.end || '', task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingWaitDate(false); @@ -507,7 +515,8 @@ export const Tasks = ( task.wait || '', task.end || '', task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingStartDate(false); @@ -529,7 +538,8 @@ export const Tasks = ( task.wait, task.end, task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingEntryDate(false); @@ -551,7 +561,8 @@ export const Tasks = ( task.wait, task.end, task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingEndDate(false); @@ -573,7 +584,8 @@ export const Tasks = ( task.wait, task.end, task.depends || [], - task.due + task.due, + task.recur || '' ); setIsEditingDueDate(false); @@ -595,13 +607,47 @@ export const Tasks = ( task.wait || '', task.end || '', task.depends, - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingDepends(false); setDependsDropdownOpen(false); }; + const handleRecurSaveClick = (task: Task) => { + if (editedRecur === 'none') { + setIsEditingRecur(false); + return; + } + + if (!editedRecur || editedRecur === '') { + setIsEditingRecur(false); + return; + } + + task.recur = editedRecur; + + handleEditTaskOnBackend( + props.email, + props.encryptionSecret, + props.UUID, + task.description, + task.tags, + task.id.toString(), + task.project, + task.start, + task.entry || '', + task.wait || '', + task.end || '', + task.depends || [], + task.due || '', + task.recur + ); + + setIsEditingRecur(false); + }; + const handleAddDependency = (uuid: string) => { if (!editedDepends.includes(uuid)) { setEditedDepends([...editedDepends, uuid]); @@ -637,6 +683,9 @@ export const Tasks = ( setEditedDepends([]); setDependsDropdownOpen(false); setDependsSearchTerm(''); + setIsEditingRecur(false); + setEditedRecur(''); + setOriginalRecur(''); } else { setSelectedTask(task); setEditedDescription(task?.description || ''); @@ -762,7 +811,8 @@ export const Tasks = ( task.wait || '', task.end || '', task.depends || [], - task.due || '' + task.due || '', + task.recur || '' ); setIsEditingTags(false); @@ -1937,14 +1987,6 @@ export const Tasks = ( )} - - Recur: - {task.recur} - - - RType: - {task.rtype} - Priority: @@ -2285,6 +2327,109 @@ export const Tasks = ( )} + + Recur: + + {isEditingRecur ? ( +
+ + + +
+ ) : ( +
+ + {task.recur || 'None'} + + +
+ )} +
+
+ + RType: + + {task.rtype || 'None'} + {!task.rtype && ( + + (Auto-set by recur) + + )} + + Urgency: {task.urgency} diff --git a/frontend/src/components/HomeComponents/Tasks/hooks.ts b/frontend/src/components/HomeComponents/Tasks/hooks.ts index f0931f96..d0c0d93d 100644 --- a/frontend/src/components/HomeComponents/Tasks/hooks.ts +++ b/frontend/src/components/HomeComponents/Tasks/hooks.ts @@ -95,6 +95,7 @@ export const editTaskOnBackend = async ({ end, depends, due, + recur, }: { email: string; encryptionSecret: string; @@ -110,6 +111,7 @@ export const editTaskOnBackend = async ({ end: string; depends: string[]; due: string; + recur: string; }) => { const response = await fetch(`${backendURL}edit-task`, { method: 'POST', @@ -127,6 +129,7 @@ export const editTaskOnBackend = async ({ end, depends, due, + recur, }), headers: { 'Content-Type': 'application/json',