From 263d3ea28c77350e4d1eae6391ee7ca7a6372784 Mon Sep 17 00:00:00 2001 From: Vasist10 <155972527+Vasist10@users.noreply.github.com> Date: Sat, 6 Dec 2025 11:36:04 +0530 Subject: [PATCH 1/2] feat: make due date optional --- backend/controllers/add_task.go | 10 +- backend/controllers/controllers_test.go | 74 ++++++++++ backend/models/request_body.go | 2 +- backend/utils/tw/taskwarrior_test.go | 9 ++ .../components/HomeComponents/Tasks/Tasks.tsx | 137 ++++++++++++------ .../Tasks/__tests__/Tasks.test.tsx | 52 +++++++ .../components/HomeComponents/Tasks/hooks.ts | 28 ++-- 7 files changed, 248 insertions(+), 64 deletions(-) diff --git a/backend/controllers/add_task.go b/backend/controllers/add_task.go index 6c6a0aac..4cdd331d 100644 --- a/backend/controllers/add_task.go +++ b/backend/controllers/add_task.go @@ -52,9 +52,11 @@ func AddTaskHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Description is required, and cannot be empty!", http.StatusBadRequest) return } - if dueDate == "" { - http.Error(w, "Due Date is required, and cannot be empty!", http.StatusBadRequest) - return + + // Handle optional due date - convert pointer to string + var dueDateStr string + if dueDate != nil && *dueDate != "" { + dueDateStr = *dueDate } logStore := models.GetLogStore() @@ -62,7 +64,7 @@ func AddTaskHandler(w http.ResponseWriter, r *http.Request) { Name: "Add Task", Execute: func() error { logStore.AddLog("INFO", fmt.Sprintf("Adding task: %s", description), uuid, "Add Task") - err := tw.AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, priority, dueDate, tags) + err := tw.AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, priority, dueDateStr, tags) if err != nil { logStore.AddLog("ERROR", fmt.Sprintf("Failed to add task: %v", err), uuid, "Add Task") return err diff --git a/backend/controllers/controllers_test.go b/backend/controllers/controllers_test.go index b21ae91d..88368f05 100644 --- a/backend/controllers/controllers_test.go +++ b/backend/controllers/controllers_test.go @@ -1,6 +1,7 @@ package controllers import ( + "bytes" "encoding/gob" "encoding/json" "net/http" @@ -122,3 +123,76 @@ func Test_LogoutHandler(t *testing.T) { session, _ := app.SessionStore.Get(req, "session-name") assert.Equal(t, -1, session.Options.MaxAge) } + +func Test_AddTaskHandler_WithDueDate(t *testing.T) { + // Initialize job queue + GlobalJobQueue = NewJobQueue() + + requestBody := map[string]interface{}{ + "email": "test@example.com", + "encryptionSecret": "secret", + "UUID": "test-uuid", + "description": "Test task", + "project": "TestProject", + "priority": "H", + "due": "2025-12-31", + "tags": []string{"test", "important"}, + } + + body, _ := json.Marshal(requestBody) + req, err := http.NewRequest("POST", "/add-task", bytes.NewBuffer(body)) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + AddTaskHandler(rr, req) + + assert.Equal(t, http.StatusAccepted, rr.Code) +} + +func Test_AddTaskHandler_WithoutDueDate(t *testing.T) { + // Initialize job queue + GlobalJobQueue = NewJobQueue() + + requestBody := map[string]interface{}{ + "email": "test@example.com", + "encryptionSecret": "secret", + "UUID": "test-uuid", + "description": "Test task without due date", + "project": "TestProject", + "priority": "M", + "tags": []string{"test"}, + } + + body, _ := json.Marshal(requestBody) + req, err := http.NewRequest("POST", "/add-task", bytes.NewBuffer(body)) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + AddTaskHandler(rr, req) + + assert.Equal(t, http.StatusAccepted, rr.Code) +} + +func Test_AddTaskHandler_MissingDescription(t *testing.T) { + requestBody := map[string]interface{}{ + "email": "test@example.com", + "encryptionSecret": "secret", + "UUID": "test-uuid", + "description": "", + "project": "TestProject", + "priority": "H", + } + + body, _ := json.Marshal(requestBody) + req, err := http.NewRequest("POST", "/add-task", bytes.NewBuffer(body)) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + AddTaskHandler(rr, req) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Description is required") +} diff --git a/backend/models/request_body.go b/backend/models/request_body.go index 8e1d97ea..f594e068 100644 --- a/backend/models/request_body.go +++ b/backend/models/request_body.go @@ -8,7 +8,7 @@ type AddTaskRequestBody struct { Description string `json:"description"` Project string `json:"project"` Priority string `json:"priority"` - DueDate string `json:"due"` + DueDate *string `json:"due"` Tags []string `json:"tags"` } type ModifyTaskRequestBody struct { diff --git a/backend/utils/tw/taskwarrior_test.go b/backend/utils/tw/taskwarrior_test.go index c8be55fb..9142c471 100644 --- a/backend/utils/tw/taskwarrior_test.go +++ b/backend/utils/tw/taskwarrior_test.go @@ -67,6 +67,15 @@ func TestAddTaskWithTags(t *testing.T) { } } +func TestAddTaskWithoutDueDate(t *testing.T) { + err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "", "H", "", nil) + if err != nil { + t.Errorf("AddTaskToTaskwarrior without due date failed: %v", err) + } else { + fmt.Println("Add task without due date passed") + } +} + 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") if err != nil { diff --git a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx index 8f0e18df..7b72d1ed 100644 --- a/frontend/src/components/HomeComponents/Tasks/Tasks.tsx +++ b/frontend/src/components/HomeComponents/Tasks/Tasks.tsx @@ -74,6 +74,7 @@ import { DatePicker } from '@/components/ui/date-picker'; import { format } from 'date-fns'; import { Taskskeleton } from './TaskSkeleton'; import { Key } from '@/components/ui/key-button'; +import { Switch } from '@/components/ui/switch'; const db = new TasksDatabase(); export let syncTasksWithTwAndDb: () => any; @@ -104,6 +105,7 @@ export const Tasks = ( due: '', tags: [] as string[], }); + const [includeDueDate, setIncludeDueDate] = useState(false); const [isCreatingNewProject, setIsCreatingNewProject] = useState(false); const [isAddTaskOpen, setIsAddTaskOpen] = useState(false); const [_isDialogOpen, setIsDialogOpen] = useState(false); @@ -352,32 +354,36 @@ export const Tasks = ( due: string, tags: string[] ) { - if (handleDate(newTask.due)) { - try { - await addTaskToBackend({ - email, - encryptionSecret, - UUID, - description, - project, - priority, - due, - tags, - backendURL: url.backendURL, - }); - - console.log('Task added successfully!'); - setNewTask({ - description: '', - priority: '', - project: '', - due: '', - tags: [], - }); - setIsAddTaskOpen(false); - } catch (error) { - console.error('Failed to add task:', error); - } + // Only validate due date if includeDueDate is checked and due is provided + if (includeDueDate && due && !handleDate(due)) { + return; // handleDate shows error toast, so just return + } + + try { + await addTaskToBackend({ + email, + encryptionSecret, + UUID, + description, + project, + priority, + due: includeDueDate ? due : undefined, + tags, + backendURL: url.backendURL, + }); + + console.log('Task added successfully!'); + setNewTask({ + description: '', + priority: '', + project: '', + due: '', + tags: [], + }); + setIncludeDueDate(false); + setIsAddTaskOpen(false); + } catch (error) { + console.error('Failed to add task:', error); } } @@ -1016,7 +1022,20 @@ export const Tasks = (
{ + setIsAddTaskOpen(open); + if (!open) { + // Reset form when dialog closes + setIncludeDueDate(false); + setNewTask({ + description: '', + priority: '', + project: '', + due: '', + tags: [], + }); + } + }} >
-