diff --git a/backend/utils/tw/add_task.go b/backend/utils/tw/add_task.go index d362f903..e506534a 100644 --- a/backend/utils/tw/add_task.go +++ b/backend/utils/tw/add_task.go @@ -3,7 +3,6 @@ package tw import ( "ccsync_backend/models" "ccsync_backend/utils" - "encoding/json" "fmt" "os" "strings" @@ -64,9 +63,6 @@ func AddTaskToTaskwarrior(req models.AddTaskRequestBody, dueDate string) error { } cmdArgs = append(cmdArgs, "wait:"+wait) } - if req.End != "" { - cmdArgs = append(cmdArgs, "end:"+req.End) - } if req.Recur != "" && dueDate != "" { cmdArgs = append(cmdArgs, "recur:"+req.Recur) } @@ -83,24 +79,28 @@ func AddTaskToTaskwarrior(req models.AddTaskRequestBody, dueDate string) error { return fmt.Errorf("failed to add task: %v\n %v", err, cmdArgs) } - if len(req.Annotations) > 0 { - output, err := utils.ExecCommandForOutputInDir(tempDir, "task", "export") + var taskID string + if req.End != "" || len(req.Annotations) > 0 { + output, err := utils.ExecCommandForOutputInDir(tempDir, "task", "+LATEST", "_ids") if err != nil { - return fmt.Errorf("failed to export tasks: %v", err) + return fmt.Errorf("failed to get latest task Id: %v", err) } - var tasks []models.Task - if err := json.Unmarshal(output, &tasks); err != nil { - return fmt.Errorf("failed to parse exported tasks: %v", err) - } + taskID = strings.TrimSpace(string(output)) + } - if len(tasks) == 0 { - return fmt.Errorf("no tasks found after creation") + if req.End != "" { + end, err := utils.ConvertISOToTaskwarriorFormat(req.End) + if err != nil { + return fmt.Errorf("unexpected end date format error: %v", err) } + doneArgs := []string{"rc.confirmation=off", taskID, "done", "end:" + end} + if err := utils.ExecCommandInDir(tempDir, "task", doneArgs...); err != nil { + return fmt.Errorf("failed to complete task with end date: %v", err) + } + } - lastTask := tasks[len(tasks)-1] - taskID := fmt.Sprintf("%d", lastTask.ID) - + if len(req.Annotations) > 0 { for _, annotation := range req.Annotations { if annotation.Description != "" { annotateArgs := []string{"rc.confirmation=off", taskID, "annotate", annotation.Description} diff --git a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx index 77b57c56..559457f8 100644 --- a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx +++ b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useRef } from 'react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; -import { DatePicker } from '@/components/ui/date-picker'; import { DateTimePicker } from '@/components/ui/date-time-picker'; import { Dialog, @@ -401,17 +400,36 @@ export const AddTaskdialog = ({ End
- (inputRefs.current.end = element)} - date={newTask.end ? new Date(newTask.end) : undefined} - onDateChange={(date) => { + date={ + newTask.end + ? new Date( + newTask.end.includes('T') + ? newTask.end + : `${newTask.end}T00:00:00` + ) + : undefined + } + onDateTimeChange={(date, hasTime) => { setNewTask({ ...newTask, - end: date ? format(date, 'yyyy-MM-dd') : '', + end: date + ? hasTime + ? date.toISOString() + : format(date, 'yyyy-MM-dd') + : '', }); }} - placeholder="Select an end date" + placeholder="Select end date and time" /> + {newTask.end && ( +
+

+ Task will be marked as completed +

+
+ )}
({ }), })); -jest.mock('@/components/ui/date-picker', () => ({ - DatePicker: ({ onDateChange, placeholder }: any) => ( - { - if (e.target.value) { - onDateChange(new Date(e.target.value)); - } else { - onDateChange(null); - } - }} - /> - ), -})); - jest.mock('@/components/ui/date-time-picker', () => ({ DateTimePicker: ({ onDateTimeChange, placeholder }: any) => (
@@ -431,6 +415,11 @@ describe('AddTaskDialog Component', () => { label: 'Entry', placeholder: 'Select entry date and time', }, + { + name: 'end', + label: 'End', + placeholder: 'Select end date and time', + }, ]; test.each(dateTimeFields)( @@ -533,75 +522,27 @@ describe('AddTaskDialog Component', () => { ); }); - describe('DatePicker fields', () => { - const dateOnlyFields = [ - { name: 'end', label: 'End', placeholder: 'Select an end date' }, - ]; - - test.each(dateOnlyFields)( - 'renders $name date picker with correct placeholder', - ({ placeholder }) => { - mockProps.isOpen = true; - render(); + describe('End date warning message', () => { + test('should show warning when end date is selected', () => { + mockProps.isOpen = true; + mockProps.newTask.end = '2026-01-18'; - const datePicker = screen.getByPlaceholderText(placeholder); - expect(datePicker).toBeInTheDocument(); - } - ); + render(); - test.each(dateOnlyFields)( - 'updates $name when user selects a date', - ({ name, placeholder }) => { - mockProps.isOpen = true; - render(); - - const datePicker = screen.getByPlaceholderText(placeholder); - fireEvent.change(datePicker, { target: { value: '2025-12-25' } }); - - expect(mockProps.setNewTask).toHaveBeenCalledWith({ - ...mockProps.newTask, - [name]: '2025-12-25', - }); - } - ); - - test.each(dateOnlyFields)( - 'allows empty $name date (optional field)', - ({ name, placeholder }) => { - mockProps.isOpen = true; - render(); - - const datePicker = screen.getByPlaceholderText(placeholder); - - fireEvent.change(datePicker, { target: { value: '2025-12-25' } }); - mockProps.setNewTask.mockClear(); - fireEvent.change(datePicker, { target: { value: '' } }); - - expect(mockProps.setNewTask).toHaveBeenCalledWith({ - ...mockProps.newTask, - [name]: '', - }); - } - ); + expect( + screen.getByText(/task will be marked as completed/i) + ).toBeInTheDocument(); + }); - test.each(dateOnlyFields)( - 'submits task with $name date when provided', - ({ name }) => { - mockProps.isOpen = true; - mockProps.newTask = { - ...mockProps.newTask, - [name]: '2025-12-25', - }; - render(); + test('should not show warning when end date is empty', () => { + mockProps.isOpen = true; - const submitButton = screen.getByRole('button', { - name: /add task/i, - }); - fireEvent.click(submitButton); + render(); - expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask); - } - ); + expect( + screen.queryByText(/task will be marked as completed/i) + ).not.toBeInTheDocument(); + }); }); });