diff --git a/backend/utils/tw/add_task.go b/backend/utils/tw/add_task.go
index ae0d974f..1109eb92 100644
--- a/backend/utils/tw/add_task.go
+++ b/backend/utils/tw/add_task.go
@@ -40,7 +40,11 @@ func AddTaskToTaskwarrior(req models.AddTaskRequestBody, dueDate string) error {
cmdArgs = append(cmdArgs, "due:"+dueDate)
}
if req.Start != "" {
- cmdArgs = append(cmdArgs, "start:"+req.Start)
+ start, err := utils.ConvertISOToTaskwarriorFormat(req.Start)
+ if err != nil {
+ return fmt.Errorf("unexpected date format error: %v", err)
+ }
+ cmdArgs = append(cmdArgs, "start:"+start)
}
if len(req.Depends) > 0 {
dependsStr := strings.Join(req.Depends, ",")
diff --git a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx
index e0788087..12846e46 100644
--- a/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx
+++ b/frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx
@@ -288,15 +288,27 @@ export const AddTaskdialog = ({
Start
- {
+ {
setNewTask({
...newTask,
- start: date ? format(date, 'yyyy-MM-dd') : '',
+ start: date
+ ? hasTime
+ ? date.toISOString()
+ : format(date, 'yyyy-MM-dd')
+ : '',
});
}}
- placeholder="Select a start date"
+ placeholder="Select start date and time"
/>
diff --git a/frontend/src/components/HomeComponents/Tasks/__tests__/AddTaskDialog.test.tsx b/frontend/src/components/HomeComponents/Tasks/__tests__/AddTaskDialog.test.tsx
index b89ec1a8..69c0b0c3 100644
--- a/frontend/src/components/HomeComponents/Tasks/__tests__/AddTaskDialog.test.tsx
+++ b/frontend/src/components/HomeComponents/Tasks/__tests__/AddTaskDialog.test.tsx
@@ -28,6 +28,25 @@ jest.mock('@/components/ui/date-picker', () => ({
),
}));
+jest.mock('@/components/ui/date-time-picker', () => ({
+ DateTimePicker: ({ onDateTimeChange, placeholder }: any) => (
+
+ {
+ if (e.target.value) {
+ const hasTime = e.target.value.includes('T');
+ onDateTimeChange(new Date(e.target.value), hasTime);
+ } else {
+ onDateTimeChange(undefined, false);
+ }
+ }}
+ />
+
+ ),
+}));
+
jest.mock('@/components/ui/select', () => {
return {
Select: ({ children, onValueChange, value }: any) => {
@@ -374,102 +393,188 @@ describe('AddTaskDialog Component', () => {
});
describe('Date Fields', () => {
- const dateFields = [
- { name: 'due', label: 'Due', placeholder: 'Select a due date' },
- { name: 'start', label: 'Start', placeholder: 'Select a start date' },
- { name: 'end', label: 'End', placeholder: 'Select an end date' },
- { name: 'entry', label: 'Entry', placeholder: 'Select an entry date' },
- { name: 'wait', label: 'Wait', placeholder: 'Select a wait date' },
- ];
-
- test.each(dateFields.filter((field) => field.name !== 'due'))(
- 'renders $name date picker with correct placeholder',
- ({ placeholder }) => {
- mockProps.isOpen = true;
- render();
-
- const datePicker = screen.getByPlaceholderText(placeholder);
- expect(datePicker).toBeInTheDocument();
- }
- );
-
- test('renders due date picker with correct placeholder', () => {
- mockProps.isOpen = true;
- render();
+ describe('DateTime Fields', () => {
+ const dateTimeFields = [
+ { name: 'due', label: 'Due', placeholder: 'Select due date and time' },
+ {
+ name: 'start',
+ label: 'Start',
+ placeholder: 'Select start date and time',
+ },
+ ];
- const dueDateButton = screen.getByText('Select due date and time');
- expect(dueDateButton).toBeInTheDocument();
- });
+ test.each(dateTimeFields)(
+ 'renders $name date-time picker with correct placeholder',
+ ({ placeholder }) => {
+ mockProps.isOpen = true;
+ render();
- test.each(dateFields.filter((field) => field.name !== 'due'))(
- 'updates $name when user selects a date',
- ({ name, placeholder }) => {
- mockProps.isOpen = true;
- render();
+ const picker = screen.getByPlaceholderText(placeholder);
+ expect(picker).toBeInTheDocument();
+ }
+ );
- const datePicker = screen.getByPlaceholderText(placeholder);
- fireEvent.change(datePicker, { target: { value: '2025-12-25' } });
+ test.each(dateTimeFields)(
+ 'updates $name with date only when no time is selected',
+ ({ name, placeholder }) => {
+ mockProps.isOpen = true;
+ render();
- expect(mockProps.setNewTask).toHaveBeenCalledWith({
- ...mockProps.newTask,
- [name]: '2025-12-25',
- });
- }
- );
+ const picker = screen.getByPlaceholderText(placeholder);
+ fireEvent.change(picker, { target: { value: '2025-12-25' } });
- // Special test for due date with DateTimePicker
- test('updates due when user selects a date and time', () => {
- mockProps.isOpen = true;
- render();
+ expect(mockProps.setNewTask).toHaveBeenLastCalledWith({
+ ...mockProps.newTask,
+ [name]: '2025-12-25',
+ });
+ }
+ );
+
+ test.each(dateTimeFields)(
+ 'updates $name with full datetime when time is selected',
+ ({ name, placeholder }) => {
+ mockProps.isOpen = true;
+ render();
+ const picker = screen.getByPlaceholderText(placeholder);
+
+ fireEvent.change(picker, {
+ target: { value: '2025-12-25T14:30:00' },
+ });
+ expect(mockProps.setNewTask).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ [name]: expect.any(String),
+ })
+ );
+
+ const callArgs = mockProps.setNewTask.mock.calls.at(-1)![0];
+ expect(callArgs[name]).toContain('T');
+ }
+ );
+
+ test.each(dateTimeFields)(
+ 'allows empty $name date (optional field)',
+ ({ name, placeholder }) => {
+ mockProps.isOpen = true;
+ render();
+
+ const picker = screen.getByPlaceholderText(placeholder);
+
+ fireEvent.change(picker, {
+ target: { value: '2025-12-25T14:30:00' },
+ });
+ mockProps.setNewTask.mockClear();
+ fireEvent.change(picker, { target: { value: '' } });
- const dueDateButton = screen.getByText('Select due date and time');
- expect(dueDateButton).toBeInTheDocument();
+ expect(mockProps.setNewTask).toHaveBeenLastCalledWith({
+ ...mockProps.newTask,
+ [name]: '',
+ });
+ }
+ );
+
+ test.each(dateTimeFields)(
+ 'submits task with $name date when provided',
+ ({ name }) => {
+ mockProps.isOpen = true;
+ mockProps.newTask = {
+ ...mockProps.newTask,
+ [name]: '2025-12-25T14:30:00.000Z',
+ };
+ render();
+
+ const picker = screen.getByPlaceholderText(
+ `Select ${name} date and time`
+ );
+ fireEvent.change(picker, {
+ target: { value: '2025-12-25T14:30:00' },
+ });
+
+ const submitButton = screen.getByRole('button', {
+ name: /add task/i,
+ });
+ fireEvent.click(submitButton);
+
+ expect(mockProps.onSubmit).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ [name]: '2025-12-25T14:30:00.000Z',
+ })
+ );
+ }
+ );
});
- test.each(dateFields.filter((field) => field.name !== 'due'))(
- 'allows empty $name date (optional field)',
- ({ name, placeholder }) => {
- mockProps.isOpen = true;
- render();
+ describe('DatePicker fields', () => {
+ const dateOnlyFields = [
+ { name: 'end', label: 'End', placeholder: 'Select an end date' },
+ { name: 'entry', label: 'Entry', placeholder: 'Select an entry date' },
+ { name: 'wait', label: 'Wait', placeholder: 'Select a wait date' },
+ ];
- const datePicker = screen.getByPlaceholderText(placeholder);
+ test.each(dateOnlyFields)(
+ 'renders $name date picker with correct placeholder',
+ ({ placeholder }) => {
+ mockProps.isOpen = true;
+ render();
- fireEvent.change(datePicker, { target: { value: '2025-12-25' } });
- mockProps.setNewTask.mockClear();
- fireEvent.change(datePicker, { target: { value: '' } });
+ const datePicker = screen.getByPlaceholderText(placeholder);
+ expect(datePicker).toBeInTheDocument();
+ }
+ );
- expect(mockProps.setNewTask).toHaveBeenCalledWith({
- ...mockProps.newTask,
- [name]: '',
- });
- }
- );
+ test.each(dateOnlyFields)(
+ 'updates $name when user selects a date',
+ ({ name, placeholder }) => {
+ mockProps.isOpen = true;
+ render();
- // Special test for due date with DateTimePicker
- test('allows empty due date (optional field)', () => {
- mockProps.isOpen = true;
- render();
+ const datePicker = screen.getByPlaceholderText(placeholder);
+ fireEvent.change(datePicker, { target: { value: '2025-12-25' } });
- const dueDateButton = screen.getByText('Select due date and time');
- expect(dueDateButton).toBeInTheDocument();
- });
+ 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);
- test.each(dateFields)(
- 'submits task with $name date when provided',
- ({ name }) => {
- mockProps.isOpen = true;
- mockProps.newTask = {
- ...mockProps.newTask,
- [name]: '2025-12-25',
- };
- render();
+ fireEvent.change(datePicker, { target: { value: '2025-12-25' } });
+ mockProps.setNewTask.mockClear();
+ fireEvent.change(datePicker, { target: { value: '' } });
- const submitButton = screen.getByRole('button', { name: /add task/i });
- fireEvent.click(submitButton);
+ expect(mockProps.setNewTask).toHaveBeenCalledWith({
+ ...mockProps.newTask,
+ [name]: '',
+ });
+ }
+ );
- expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask);
- }
- );
+ test.each(dateOnlyFields)(
+ 'submits task with $name date when provided',
+ ({ name }) => {
+ mockProps.isOpen = true;
+ mockProps.newTask = {
+ ...mockProps.newTask,
+ [name]: '2025-12-25',
+ };
+ render();
+
+ const submitButton = screen.getByRole('button', {
+ name: /add task/i,
+ });
+ fireEvent.click(submitButton);
+
+ expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask);
+ }
+ );
+ });
});
describe('Depends Field', () => {