Skip to content

Commit bf6f416

Browse files
committed
feat(AddTask): Add entry date field to Add Task dialog
- Add entry date field to backend request body and Taskwarrior CLI - Implement entry date picker in AddTaskDialog frontend component - Update type definitions and hooks to support entry field - Add comprehensive frontend and backend tests for the new field Issue: #188
1 parent a518d92 commit bf6f416

File tree

9 files changed

+125
-8
lines changed

9 files changed

+125
-8
lines changed

backend/controllers/add_task.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func AddTaskHandler(w http.ResponseWriter, r *http.Request) {
4747
priority := requestBody.Priority
4848
dueDate := requestBody.DueDate
4949
start := requestBody.Start
50+
entryDate := requestBody.EntryDate
5051
waitDate := requestBody.WaitDate
5152
end := requestBody.End
5253
recur := requestBody.Recur
@@ -67,7 +68,7 @@ func AddTaskHandler(w http.ResponseWriter, r *http.Request) {
6768
Name: "Add Task",
6869
Execute: func() error {
6970
logStore.AddLog("INFO", fmt.Sprintf("Adding task: %s", description), uuid, "Add Task")
70-
err := tw.AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, priority, dueDateStr, start, waitDate, end, recur, tags, annotations)
71+
err := tw.AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, priority, dueDateStr, start, entryDate, waitDate, end, recur, tags, annotations)
7172
if err != nil {
7273
logStore.AddLog("ERROR", fmt.Sprintf("Failed to add task: %v", err), uuid, "Add Task")
7374
return err

backend/models/request_body.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type AddTaskRequestBody struct {
1010
Priority string `json:"priority"`
1111
DueDate *string `json:"due"`
1212
Start string `json:"start"`
13+
EntryDate string `json:"entry"`
1314
WaitDate string `json:"wait"`
1415
End string `json:"end"`
1516
Recur string `json:"recur"`

backend/utils/tw/add_task.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
// add task to the user's tw client
13-
func AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, priority, dueDate, start, waitDate string, end, recur string, tags []string, annotations []models.Annotation) error {
13+
func AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, priority, dueDate, start, entryDate string, waitDate string, end, recur string, tags []string, annotations []models.Annotation) error {
1414
if err := utils.ExecCommand("rm", "-rf", "/root/.task"); err != nil {
1515
return fmt.Errorf("error deleting Taskwarrior data: %v", err)
1616
}
@@ -43,6 +43,9 @@ func AddTaskToTaskwarrior(email, encryptionSecret, uuid, description, project, p
4343
if start != "" {
4444
cmdArgs = append(cmdArgs, "start:"+start)
4545
}
46+
if entryDate != "" {
47+
cmdArgs = append(cmdArgs, "entry:"+entryDate)
48+
}
4649
if waitDate != "" {
4750
cmdArgs = append(cmdArgs, "wait:"+waitDate)
4851
}

backend/utils/tw/taskwarrior_test.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func TestSetTaskwarriorConfig(t *testing.T) {
1414
fmt.Println("SetTaskwarriorConfig test passed")
1515
}
1616
}
17+
1718
func TestSyncTaskwarrior(t *testing.T) {
1819
err := SyncTaskwarrior("./")
1920
if err != nil {
@@ -42,7 +43,7 @@ func TestExportTasks(t *testing.T) {
4243
}
4344

4445
func TestAddTaskToTaskwarrior(t *testing.T) {
45-
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "", "H", "2025-03-03", "2025-03-01", "2025-03-01", "2025-03-03", "daily", nil, []models.Annotation{{Description: "note"}})
46+
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "", "H", "2025-03-03", "2025-03-01", "2025-03-01", "2025-03-01", "2025-03-03", "daily", nil, []models.Annotation{{Description: "note"}})
4647
if err != nil {
4748
t.Errorf("AddTaskToTaskwarrior failed: %v", err)
4849
} else {
@@ -51,14 +52,23 @@ func TestAddTaskToTaskwarrior(t *testing.T) {
5152
}
5253

5354
func TestAddTaskToTaskwarriorWithWaitDate(t *testing.T) {
54-
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "project", "H", "2025-03-03", "2025-03-04", "2025-03-04", "2025-03-04", "", nil, []models.Annotation{})
55+
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "project", "H", "2025-03-03", "2025-03-04", "2025-03-04", "2025-03-04", "2025-03-04", "", nil, []models.Annotation{})
5556
if err != nil {
5657
t.Errorf("AddTaskToTaskwarrior with wait date failed: %v", err)
5758
} else {
5859
fmt.Println("Add task with wait date passed")
5960
}
6061
}
6162

63+
func TestAddTaskToTaskwarriorWithEntryDate(t *testing.T) {
64+
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "project", "H", "2025-03-05", "2025-03-04", "2025-03-04", "2025-03-04", "2025-03-10", "", nil, nil)
65+
if err != nil {
66+
t.Errorf("AddTaskToTaskwarrior failed: %v", err)
67+
} else {
68+
fmt.Println("Add task with entry date passed ")
69+
}
70+
}
71+
6272
func TestCompleteTaskInTaskwarrior(t *testing.T) {
6373
err := CompleteTaskInTaskwarrior("email", "encryptionSecret", "client_id", "taskuuid")
6474
if err != nil {
@@ -69,16 +79,25 @@ func TestCompleteTaskInTaskwarrior(t *testing.T) {
6979
}
7080

7181
func TestAddTaskWithTags(t *testing.T) {
72-
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "", "H", "2025-03-03", "2025-03-01", "2025-03-01", "2025-03-03", "daily", []string{"work", "important"}, []models.Annotation{{Description: "note"}})
82+
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "", "H", "2025-03-03", "2025-03-01", "2025-03-01", "2025-03-01", "2025-03-03", "daily", []string{"work", "important"}, []models.Annotation{{Description: "note"}})
7383
if err != nil {
7484
t.Errorf("AddTaskToTaskwarrior with tags failed: %v", err)
7585
} else {
7686
fmt.Println("Add task with tags passed")
7787
}
7888
}
7989

90+
func TestAddTaskToTaskwarriorWithEntryDateAndTags(t *testing.T) {
91+
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "project", "H", "2025-03-05", "2025-03-04", "2025-03-04", "2025-03-04", "2025-03-10", "", []string{"work", "important"}, nil)
92+
if err != nil {
93+
t.Errorf("AddTaskToTaskwarrior with entry date and tags failed: %v", err)
94+
} else {
95+
fmt.Println("Add task with entry date and tags passed")
96+
}
97+
}
98+
8099
func TestAddTaskToTaskwarriorWithWaitDateWithTags(t *testing.T) {
81-
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "project", "H", "2025-03-03", "2025-03-04", "2025-03-04", "2025-03-04", "", []string{"work", "important"}, []models.Annotation{})
100+
err := AddTaskToTaskwarrior("email", "encryption_secret", "clientId", "description", "project", "H", "2025-03-03", "2025-03-04", "2025-03-04", "2025-03-04", "2025-03-04", "", []string{"work", "important"}, []models.Annotation{})
82101
if err != nil {
83102
t.Errorf("AddTaskToTaskwarrior with wait date failed: %v", err)
84103
} else {

frontend/src/components/HomeComponents/Tasks/AddTaskDialog.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@ export const AddTaskdialog = ({
270270
/>
271271
</div>
272272
</div>
273+
<div className="grid grid-cols-4 items-center gap-4">
274+
<Label htmlFor="entry" className="text-right">
275+
Entry
276+
</Label>
277+
<div className="col-span-3">
278+
<DatePicker
279+
date={newTask.entry ? new Date(newTask.entry) : undefined}
280+
onDateChange={(date) => {
281+
setNewTask({
282+
...newTask,
283+
entry: date ? format(date, 'yyyy-MM-dd') : '',
284+
});
285+
}}
286+
placeholder="Select an entry date"
287+
/>
288+
</div>
289+
</div>
273290
<div className="grid grid-cols-4 items-center gap-4">
274291
<Label htmlFor="wait" className="text-right">
275292
Wait

frontend/src/components/HomeComponents/Tasks/Tasks.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export const Tasks = (
7373
project: '',
7474
due: '',
7575
start: '',
76+
entry: '',
7677
wait: '',
7778
end: '',
7879
recur: '',
@@ -311,6 +312,7 @@ export const Tasks = (
311312
priority: task.priority,
312313
due: task.due || undefined,
313314
start: task.start || '',
315+
entry: task.entry,
314316
wait: task.wait,
315317
end: task.end || '',
316318
recur: task.recur || '',
@@ -326,6 +328,7 @@ export const Tasks = (
326328
project: '',
327329
due: '',
328330
start: '',
331+
entry: '',
329332
wait: '',
330333
end: '',
331334
recur: '',

frontend/src/components/HomeComponents/Tasks/__tests__/AddTaskDialog.test.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { AddTaskdialog } from '../AddTaskDialog';
33
import '@testing-library/jest-dom';
44

55
jest.mock('date-fns', () => ({
6-
format: jest.fn(() => '2024-12-25'),
6+
format: jest.fn((date) => {
7+
const d = new Date(date);
8+
const year = d.getFullYear();
9+
const month = String(d.getMonth() + 1).padStart(2, '0');
10+
const day = String(d.getDate()).padStart(2, '0');
11+
return `${year}-${month}-${day}`;
12+
}),
713
}));
814

915
jest.mock('@/components/ui/date-picker', () => ({
@@ -59,6 +65,7 @@ describe('AddTaskDialog Component', () => {
5965
project: '',
6066
due: '',
6167
start: '',
68+
entry: '',
6269
wait: '',
6370
end: '',
6471
recur: '',
@@ -224,6 +231,7 @@ describe('AddTaskDialog Component', () => {
224231
project: 'Work',
225232
due: '2024-12-25',
226233
start: '',
234+
entry: '2025-12-20',
227235
wait: '2025-12-20',
228236
end: '',
229237
recur: '',
@@ -313,7 +321,7 @@ describe('AddTaskDialog Component', () => {
313321

314322
expect(mockProps.setNewTask).toHaveBeenCalledWith({
315323
...mockProps.newTask,
316-
wait: '2024-12-25', // Mocked format() always returns this value regardless of input
324+
wait: '2025-12-20',
317325
});
318326
});
319327

@@ -342,4 +350,65 @@ describe('AddTaskDialog Component', () => {
342350

343351
expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask);
344352
});
353+
354+
test('renders entry date picker with correct placeholder', () => {
355+
mockProps.isOpen = true;
356+
render(<AddTaskdialog {...mockProps} />);
357+
358+
const entryDatePicker =
359+
screen.getByPlaceholderText(/select an entry date/i);
360+
expect(entryDatePicker).toBeInTheDocument();
361+
});
362+
363+
test('updates entry when user selects a date', () => {
364+
mockProps.isOpen = true;
365+
render(<AddTaskdialog {...mockProps} />);
366+
367+
const entryDatePicker =
368+
screen.getByPlaceholderText(/select an entry date/i);
369+
fireEvent.change(entryDatePicker, { target: { value: '2025-12-20' } });
370+
371+
expect(mockProps.setNewTask).toHaveBeenCalledWith({
372+
...mockProps.newTask,
373+
entry: '2025-12-20',
374+
});
375+
});
376+
377+
test('submits task with entry date when provided', () => {
378+
mockProps.isOpen = true;
379+
mockProps.newTask = {
380+
description: 'Test task',
381+
priority: 'H',
382+
project: 'Work',
383+
due: '2024-12-25',
384+
entry: '2025-12-20',
385+
tags: ['urgent'],
386+
annotations: [],
387+
};
388+
render(<AddTaskdialog {...mockProps} />);
389+
390+
const submitButton = screen.getByRole('button', { name: /add task/i });
391+
fireEvent.click(submitButton);
392+
393+
expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask);
394+
});
395+
396+
test('allows empty entry date (optional field)', () => {
397+
mockProps.isOpen = true;
398+
mockProps.newTask = {
399+
description: 'Test task',
400+
priority: 'M',
401+
project: '',
402+
due: '',
403+
entry: '',
404+
tags: [],
405+
annotations: [],
406+
};
407+
render(<AddTaskdialog {...mockProps} />);
408+
409+
const submitButton = screen.getByRole('button', { name: /add task/i });
410+
fireEvent.click(submitButton);
411+
412+
expect(mockProps.onSubmit).toHaveBeenCalledWith(mockProps.newTask);
413+
});
345414
});

frontend/src/components/HomeComponents/Tasks/hooks.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const addTaskToBackend = async ({
4343
priority,
4444
due,
4545
start,
46+
entry,
4647
wait,
4748
end,
4849
recur,
@@ -58,6 +59,7 @@ export const addTaskToBackend = async ({
5859
priority: string;
5960
due?: string;
6061
start: string;
62+
entry: string;
6163
wait: string;
6264
end?: string;
6365
recur: string;
@@ -72,6 +74,7 @@ export const addTaskToBackend = async ({
7274
description,
7375
project,
7476
priority,
77+
entry,
7578
wait,
7679
tags,
7780
};

frontend/src/components/utils/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface TaskFormData {
103103
project: string;
104104
due: string;
105105
start: string;
106+
entry: string;
106107
wait: string;
107108
end: string;
108109
recur: string;

0 commit comments

Comments
 (0)