Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions backend/utils/tw/add_task.go
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed end from cmdArgs (not allowed on pending tasks). Using task done end:<date> to set end date and mark task completed, the way in Taskwarrior.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package tw
import (
"ccsync_backend/models"
"ccsync_backend/utils"
"encoding/json"
"fmt"
"os"
"strings"
Expand Down Expand Up @@ -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)
}
Expand All @@ -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}
Expand Down
30 changes: 24 additions & 6 deletions frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -401,17 +400,36 @@ export const AddTaskdialog = ({
End
</Label>
<div className="col-span-3">
<DatePicker
<DateTimePicker
ref={(element) => (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 && (
<div className="mt-1.5 pl-2.5 border-l-2 border-amber-500/60">
<p className="text-xs text-amber-400 leading-tight">
Task will be marked as completed
</p>
</div>
)}
</div>
</div>
<div
Expand Down
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the tests and added test for warning message

Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,6 @@ jest.mock('date-fns', () => ({
}),
}));

jest.mock('@/components/ui/date-picker', () => ({
DatePicker: ({ onDateChange, placeholder }: any) => (
<input
data-testid="date-picker"
placeholder={placeholder}
onChange={(e) => {
if (e.target.value) {
onDateChange(new Date(e.target.value));
} else {
onDateChange(null);
}
}}
/>
),
}));

jest.mock('@/components/ui/date-time-picker', () => ({
DateTimePicker: ({ onDateTimeChange, placeholder }: any) => (
<div>
Expand Down Expand Up @@ -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)(
Expand Down Expand Up @@ -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(<AddTaskdialog {...mockProps} />);
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(<AddTaskdialog {...mockProps} />);

test.each(dateOnlyFields)(
'updates $name when user selects a date',
({ name, placeholder }) => {
mockProps.isOpen = true;
render(<AddTaskdialog {...mockProps} />);

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(<AddTaskdialog {...mockProps} />);

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(<AddTaskdialog {...mockProps} />);
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(<AddTaskdialog {...mockProps} />);

expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask);
}
);
expect(
screen.queryByText(/task will be marked as completed/i)
).not.toBeInTheDocument();
});
});
});

Expand Down
Loading