Skip to content

Commit ebf6c3e

Browse files
authored
feat: make due field editable Added due field editing functionality to task details dialog. Users can now set and edit task due dates using a DatePicker. (#234)
1 parent 6f74525 commit ebf6c3e

File tree

6 files changed

+154
-17
lines changed

6 files changed

+154
-17
lines changed

backend/controllers/edit_task.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) {
5151
wait := requestBody.Wait
5252
end := requestBody.End
5353
depends := requestBody.Depends
54+
due := requestBody.Due
5455

5556
if taskID == "" {
5657
http.Error(w, "taskID is required", http.StatusBadRequest)
@@ -62,7 +63,7 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) {
6263
Name: "Edit Task",
6364
Execute: func() error {
6465
logStore.AddLog("INFO", fmt.Sprintf("Editing task ID: %s", taskID), uuid, "Edit Task")
65-
err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID, tags, project, start, entry, wait, end, depends)
66+
err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID, tags, project, start, entry, wait, end, depends, due)
6667
if err != nil {
6768
logStore.AddLog("ERROR", fmt.Sprintf("Failed to edit task ID %s: %v", taskID, err), uuid, "Edit Task")
6869
return err

backend/models/request_body.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type EditTaskRequestBody struct {
3636
Wait string `json:"wait"`
3737
End string `json:"end"`
3838
Depends []string `json:"depends"`
39+
Due string `json:"due"`
3940
}
4041
type CompleteTaskRequestBody struct {
4142
Email string `json:"email"`

backend/utils/tw/edit_task.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88
)
99

10-
func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID string, tags []string, project string, start string, entry string, wait string, end string, depends []string) error {
10+
func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID string, tags []string, project string, start string, entry string, wait string, end string, depends []string, due string) error {
1111
if err := utils.ExecCommand("rm", "-rf", "/root/.task"); err != nil {
1212
return fmt.Errorf("error deleting Taskwarrior data: %v", err)
1313
}
@@ -102,6 +102,16 @@ func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID st
102102
}
103103
}
104104

105+
// Handle due date
106+
if due != "" {
107+
// Convert `2025-11-29` -> `2025-11-29T00:00:00`
108+
formattedDue := due + "T00:00:00"
109+
110+
if err := utils.ExecCommand("task", taskID, "modify", "due:"+formattedDue); err != nil {
111+
return fmt.Errorf("failed to set due date %s: %v", formattedDue, err)
112+
}
113+
}
114+
105115
// Sync Taskwarrior again
106116
if err := SyncTaskwarrior(tempDir); err != nil {
107117
return err

backend/utils/tw/taskwarrior_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestSyncTaskwarrior(t *testing.T) {
2323
}
2424

2525
func TestEditTaskInATaskwarrior(t *testing.T) {
26-
err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", nil, "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)
26+
err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", nil, "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")
2727
if err != nil {
2828
t.Errorf("EditTaskInTaskwarrior() failed: %v", err)
2929
} else {
@@ -68,7 +68,7 @@ func TestAddTaskWithTags(t *testing.T) {
6868
}
6969

7070
func TestEditTaskWithTagAddition(t *testing.T) {
71-
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)
71+
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")
7272
if err != nil {
7373
t.Errorf("EditTaskInTaskwarrior with tag addition failed: %v", err)
7474
} else {
@@ -77,7 +77,7 @@ func TestEditTaskWithTagAddition(t *testing.T) {
7777
}
7878

7979
func TestEditTaskWithTagRemoval(t *testing.T) {
80-
err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"-work", "-lowpriority"}, "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)
80+
err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"-work", "-lowpriority"}, "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")
8181
if err != nil {
8282
t.Errorf("EditTaskInTaskwarrior with tag removal failed: %v", err)
8383
} else {
@@ -86,7 +86,7 @@ func TestEditTaskWithTagRemoval(t *testing.T) {
8686
}
8787

8888
func TestEditTaskWithMixedTagOperations(t *testing.T) {
89-
err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "-work", "normal"}, "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)
89+
err := EditTaskInTaskwarrior("uuid", "description", "email", "encryptionSecret", "taskuuid", []string{"+urgent", "-work", "normal"}, "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")
9090
if err != nil {
9191
t.Errorf("EditTaskInTaskwarrior with mixed tag operations failed: %v", err)
9292
} else {

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

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ export const Tasks = (
130130
const [editedEntryDate, setEditedEntryDate] = useState('');
131131
const [isEditingEndDate, setIsEditingEndDate] = useState(false);
132132
const [editedEndDate, setEditedEndDate] = useState('');
133+
const [isEditingDueDate, setIsEditingDueDate] = useState(false);
134+
const [editedDueDate, setEditedDueDate] = useState('');
133135
const [isEditingDepends, setIsEditingDepends] = useState(false);
134136
const [editedDepends, setEditedDepends] = useState<string[]>([]);
135137
const [dependsDropdownOpen, setDependsDropdownOpen] = useState(false);
@@ -376,7 +378,8 @@ export const Tasks = (
376378
entry: string,
377379
wait: string,
378380
end: string,
379-
depends: string[]
381+
depends: string[],
382+
due: string
380383
) {
381384
try {
382385
await editTaskOnBackend({
@@ -393,6 +396,7 @@ export const Tasks = (
393396
wait,
394397
end,
395398
depends,
399+
due,
396400
});
397401

398402
console.log('Task edited successfully!');
@@ -439,7 +443,8 @@ export const Tasks = (
439443
task.entry || '',
440444
task.wait || '',
441445
task.end || '',
442-
task.depends || []
446+
task.depends || [],
447+
task.due || ''
443448
);
444449
setIsEditing(false);
445450
};
@@ -458,7 +463,8 @@ export const Tasks = (
458463
task.entry || '',
459464
task.wait || '',
460465
task.end || '',
461-
task.depends || []
466+
task.depends || [],
467+
task.due || ''
462468
);
463469
setIsEditingProject(false);
464470
};
@@ -478,7 +484,8 @@ export const Tasks = (
478484
task.entry || '',
479485
task.wait,
480486
task.end || '',
481-
task.depends || []
487+
task.depends || [],
488+
task.due || ''
482489
);
483490

484491
setIsEditingWaitDate(false);
@@ -499,7 +506,8 @@ export const Tasks = (
499506
task.entry || '',
500507
task.wait || '',
501508
task.end || '',
502-
task.depends || []
509+
task.depends || [],
510+
task.due || ''
503511
);
504512

505513
setIsEditingStartDate(false);
@@ -520,7 +528,8 @@ export const Tasks = (
520528
task.entry,
521529
task.wait,
522530
task.end,
523-
task.depends || []
531+
task.depends || [],
532+
task.due || ''
524533
);
525534

526535
setIsEditingEntryDate(false);
@@ -541,12 +550,35 @@ export const Tasks = (
541550
task.entry,
542551
task.wait,
543552
task.end,
544-
task.depends || []
553+
task.depends || [],
554+
task.due || ''
545555
);
546556

547557
setIsEditingEndDate(false);
548558
};
549559

560+
const handleDueDateSaveClick = (task: Task) => {
561+
task.due = editedDueDate;
562+
563+
handleEditTaskOnBackend(
564+
props.email,
565+
props.encryptionSecret,
566+
props.UUID,
567+
task.description,
568+
task.tags,
569+
task.id.toString(),
570+
task.project,
571+
task.start,
572+
task.entry,
573+
task.wait,
574+
task.end,
575+
task.depends || [],
576+
task.due
577+
);
578+
579+
setIsEditingDueDate(false);
580+
};
581+
550582
const handleDependsSaveClick = (task: Task) => {
551583
task.depends = editedDepends;
552584

@@ -562,7 +594,8 @@ export const Tasks = (
562594
task.entry || '',
563595
task.wait || '',
564596
task.end || '',
565-
task.depends
597+
task.depends,
598+
task.due || ''
566599
);
567600

568601
setIsEditingDepends(false);
@@ -598,6 +631,8 @@ export const Tasks = (
598631
setEditedEntryDate('');
599632
setIsEditingEndDate(false);
600633
setEditedEndDate('');
634+
setIsEditingDueDate(false);
635+
setEditedDueDate('');
601636
setIsEditingDepends(false);
602637
setEditedDepends([]);
603638
setDependsDropdownOpen(false);
@@ -726,7 +761,8 @@ export const Tasks = (
726761
task.entry || '',
727762
task.wait || '',
728763
task.end || '',
729-
task.depends || []
764+
task.depends || [],
765+
task.due || ''
730766
);
731767

732768
setIsEditingTags(false);
@@ -1227,7 +1263,9 @@ export const Tasks = (
12271263
<TableRow
12281264
id={`task-row-${task.id}`}
12291265
key={index}
1230-
className={`border-b cursor-pointer ${selectedIndex === index ? 'bg-muted/50' : ''}`}
1266+
className={`border-b cursor-pointer ${
1267+
selectedIndex === index ? 'bg-muted/50' : ''
1268+
}`}
12311269
>
12321270
{/* Display task details */}
12331271
<TableCell className="py-2">
@@ -1377,7 +1415,91 @@ export const Tasks = (
13771415
<TableRow>
13781416
<TableCell>Due:</TableCell>
13791417
<TableCell>
1380-
{formattedDate(task.due)}
1418+
{isEditingDueDate ? (
1419+
<div className="flex items-center gap-2">
1420+
<DatePicker
1421+
date={
1422+
editedDueDate &&
1423+
editedDueDate !== ''
1424+
? (() => {
1425+
try {
1426+
const dateStr =
1427+
editedDueDate.includes(
1428+
'T'
1429+
)
1430+
? editedDueDate.split(
1431+
'T'
1432+
)[0]
1433+
: editedDueDate;
1434+
const parsed =
1435+
new Date(
1436+
dateStr +
1437+
'T00:00:00'
1438+
);
1439+
return isNaN(
1440+
parsed.getTime()
1441+
)
1442+
? undefined
1443+
: parsed;
1444+
} catch {
1445+
return undefined;
1446+
}
1447+
})()
1448+
: undefined
1449+
}
1450+
onDateChange={(date) =>
1451+
setEditedDueDate(
1452+
date
1453+
? format(
1454+
date,
1455+
'yyyy-MM-dd'
1456+
)
1457+
: ''
1458+
)
1459+
}
1460+
placeholder="Select due date"
1461+
/>
1462+
<Button
1463+
variant="ghost"
1464+
size="icon"
1465+
onClick={() =>
1466+
handleDueDateSaveClick(task)
1467+
}
1468+
>
1469+
<CheckIcon className="h-4 w-4 text-green-500" />
1470+
</Button>
1471+
<Button
1472+
variant="ghost"
1473+
size="icon"
1474+
onClick={() =>
1475+
setIsEditingDueDate(false)
1476+
}
1477+
>
1478+
<XIcon className="h-4 w-4 text-red-500" />
1479+
</Button>
1480+
</div>
1481+
) : (
1482+
<>
1483+
<span>
1484+
{formattedDate(task.due)}
1485+
</span>
1486+
<Button
1487+
variant="ghost"
1488+
size="icon"
1489+
onClick={() => {
1490+
setIsEditingDueDate(true);
1491+
const dueDate = task.due
1492+
? task.due.includes('T')
1493+
? task.due.split('T')[0]
1494+
: task.due
1495+
: '';
1496+
setEditedDueDate(dueDate);
1497+
}}
1498+
>
1499+
<PencilIcon className="h-4 w-4 text-gray-500" />
1500+
</Button>
1501+
</>
1502+
)}
13811503
</TableCell>
13821504
</TableRow>
13831505
<TableRow>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const editTaskOnBackend = async ({
9494
wait,
9595
end,
9696
depends,
97+
due,
9798
}: {
9899
email: string;
99100
encryptionSecret: string;
@@ -108,6 +109,7 @@ export const editTaskOnBackend = async ({
108109
wait: string;
109110
end: string;
110111
depends: string[];
112+
due: string;
111113
}) => {
112114
const response = await fetch(`${backendURL}edit-task`, {
113115
method: 'POST',
@@ -124,6 +126,7 @@ export const editTaskOnBackend = async ({
124126
wait,
125127
end,
126128
depends,
129+
due,
127130
}),
128131
headers: {
129132
'Content-Type': 'application/json',

0 commit comments

Comments
 (0)