Skip to content

Commit 8681347

Browse files
authored
feat: add end date editing to task details (#202)
Users can now modify task end dates using the date picker
1 parent db0c913 commit 8681347

6 files changed

Lines changed: 142 additions & 14 deletions

File tree

backend/controllers/edit_task.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) {
4949
start := requestBody.Start
5050
entry := requestBody.Entry
5151
wait := requestBody.Wait
52+
end := requestBody.End
5253

5354
if taskID == "" {
5455
http.Error(w, "taskID is required", http.StatusBadRequest)
@@ -60,7 +61,7 @@ func EditTaskHandler(w http.ResponseWriter, r *http.Request) {
6061
Name: "Edit Task",
6162
Execute: func() error {
6263
logStore.AddLog("INFO", fmt.Sprintf("Editing task ID: %s", taskID), uuid, "Edit Task")
63-
err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID, tags, project, start, entry, wait)
64+
err := tw.EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID, tags, project, start, entry, wait, end)
6465
if err != nil {
6566
logStore.AddLog("ERROR", fmt.Sprintf("Failed to edit task ID %s: %v", taskID, err), uuid, "Edit Task")
6667
return err

backend/models/request_body.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type EditTaskRequestBody struct {
3434
Start string `json:"start"`
3535
Entry string `json:"entry"`
3636
Wait string `json:"wait"`
37+
End string `json:"end"`
3738
}
3839
type CompleteTaskRequestBody struct {
3940
Email string `json:"email"`

backend/utils/tw/edit_task.go

Lines changed: 8 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) error {
10+
func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID string, tags []string, project string, start string, entry string, wait string, end string) error {
1111
if err := utils.ExecCommand("rm", "-rf", "/root/.task"); err != nil {
1212
return fmt.Errorf("error deleting Taskwarrior data: %v", err)
1313
}
@@ -87,6 +87,13 @@ func EditTaskInTaskwarrior(uuid, description, email, encryptionSecret, taskID st
8787
}
8888
}
8989

90+
// Handle end date
91+
if end != "" {
92+
if err := utils.ExecCommand("task", taskID, "modify", "end:"+end); err != nil {
93+
return fmt.Errorf("failed to set end date %s: %v", end, err)
94+
}
95+
}
96+
9097
// Sync Taskwarrior again
9198
if err := SyncTaskwarrior(tempDir); err != nil {
9299
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")
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")
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")
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")
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")
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")
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")
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")
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: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export const Tasks = (
124124
const [editedStartDate, setEditedStartDate] = useState('');
125125
const [isEditingEntryDate, setIsEditingEntryDate] = useState(false);
126126
const [editedEntryDate, setEditedEntryDate] = useState('');
127+
const [isEditingEndDate, setIsEditingEndDate] = useState(false);
128+
const [editedEndDate, setEditedEndDate] = useState('');
127129
const [searchTerm, setSearchTerm] = useState('');
128130
const [lastSyncTime, setLastSyncTime] = useState<number | null>(null);
129131

@@ -327,7 +329,8 @@ export const Tasks = (
327329
project: string,
328330
start: string,
329331
entry: string,
330-
wait: string
332+
wait: string,
333+
end: string
331334
) {
332335
try {
333336
await editTaskOnBackend({
@@ -342,6 +345,7 @@ export const Tasks = (
342345
start,
343346
entry,
344347
wait,
348+
end,
345349
});
346350

347351
console.log('Task edited successfully!');
@@ -387,7 +391,8 @@ export const Tasks = (
387391
task.project,
388392
task.start,
389393
task.entry || '',
390-
task.wait
394+
task.wait || '',
395+
task.end || ''
391396
);
392397
setIsEditing(false);
393398
};
@@ -404,7 +409,8 @@ export const Tasks = (
404409
task.project,
405410
task.start,
406411
task.entry || '',
407-
task.wait
412+
task.wait || '',
413+
task.end || ''
408414
);
409415
setIsEditingProject(false);
410416
};
@@ -422,7 +428,8 @@ export const Tasks = (
422428
task.project,
423429
task.start,
424430
task.entry || '',
425-
task.wait
431+
task.wait,
432+
task.end || ''
426433
);
427434

428435
setIsEditingWaitDate(false);
@@ -441,7 +448,8 @@ export const Tasks = (
441448
task.project,
442449
task.start,
443450
task.entry || '',
444-
task.wait
451+
task.wait || '',
452+
task.end || ''
445453
);
446454

447455
setIsEditingStartDate(false);
@@ -460,12 +468,33 @@ export const Tasks = (
460468
task.project,
461469
task.start,
462470
task.entry,
463-
task.wait
471+
task.wait,
472+
task.end
464473
);
465474

466475
setIsEditingEntryDate(false);
467476
};
468477

478+
const handleEndDateSaveClick = (task: Task) => {
479+
task.end = editedEndDate;
480+
481+
handleEditTaskOnBackend(
482+
props.email,
483+
props.encryptionSecret,
484+
props.UUID,
485+
task.description,
486+
task.tags,
487+
task.id.toString(),
488+
task.project,
489+
task.start,
490+
task.entry,
491+
task.wait,
492+
task.end
493+
);
494+
495+
setIsEditingEndDate(false);
496+
};
497+
469498
const handleCancelClick = () => {
470499
setIsEditing(false);
471500
};
@@ -483,6 +512,8 @@ export const Tasks = (
483512
setEditedStartDate('');
484513
setIsEditingEntryDate(false);
485514
setEditedEntryDate('');
515+
setIsEditingEndDate(false);
516+
setEditedEndDate('');
486517
} else {
487518
setSelectedTask(task);
488519
setEditedDescription(task?.description || '');
@@ -557,7 +588,8 @@ export const Tasks = (
557588
task.project,
558589
task.start,
559590
task.entry || '',
560-
task.wait
591+
task.wait || '',
592+
task.end || ''
561593
);
562594

563595
setIsEditingTags(false); // Exit editing mode
@@ -1176,7 +1208,91 @@ export const Tasks = (
11761208
<TableRow>
11771209
<TableCell>End:</TableCell>
11781210
<TableCell>
1179-
{formattedDate(task.end)}
1211+
{isEditingEndDate ? (
1212+
<div className="flex items-center gap-2">
1213+
<DatePicker
1214+
date={
1215+
editedEndDate &&
1216+
editedEndDate !== ''
1217+
? (() => {
1218+
try {
1219+
const dateStr =
1220+
editedEndDate.includes(
1221+
'T'
1222+
)
1223+
? editedEndDate.split(
1224+
'T'
1225+
)[0]
1226+
: editedEndDate;
1227+
const parsed =
1228+
new Date(
1229+
dateStr +
1230+
'T00:00:00'
1231+
);
1232+
return isNaN(
1233+
parsed.getTime()
1234+
)
1235+
? undefined
1236+
: parsed;
1237+
} catch {
1238+
return undefined;
1239+
}
1240+
})()
1241+
: undefined
1242+
}
1243+
onDateChange={(date) =>
1244+
setEditedEndDate(
1245+
date
1246+
? format(
1247+
date,
1248+
'yyyy-MM-dd'
1249+
)
1250+
: ''
1251+
)
1252+
}
1253+
placeholder="Select end date"
1254+
/>
1255+
<Button
1256+
variant="ghost"
1257+
size="icon"
1258+
onClick={() =>
1259+
handleEndDateSaveClick(task)
1260+
}
1261+
>
1262+
<CheckIcon className="h-4 w-4 text-green-500" />
1263+
</Button>
1264+
<Button
1265+
variant="ghost"
1266+
size="icon"
1267+
onClick={() =>
1268+
setIsEditingEndDate(false)
1269+
}
1270+
>
1271+
<XIcon className="h-4 w-4 text-red-500" />
1272+
</Button>
1273+
</div>
1274+
) : (
1275+
<div className="flex items-center">
1276+
<span>
1277+
{formattedDate(task.end)}
1278+
</span>
1279+
<Button
1280+
variant="ghost"
1281+
size="icon"
1282+
onClick={() => {
1283+
setIsEditingEndDate(true);
1284+
const endDate = task.end
1285+
? task.end.includes('T')
1286+
? task.end.split('T')[0]
1287+
: task.end
1288+
: '';
1289+
setEditedEndDate(endDate);
1290+
}}
1291+
>
1292+
<PencilIcon className="h-4 w-4 text-gray-500" />
1293+
</Button>
1294+
</div>
1295+
)}
11801296
</TableCell>
11811297
</TableRow>
11821298
<TableRow>

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export const editTaskOnBackend = async ({
9292
start,
9393
entry,
9494
wait,
95+
end,
9596
}: {
9697
email: string;
9798
encryptionSecret: string;
@@ -104,6 +105,7 @@ export const editTaskOnBackend = async ({
104105
start: string;
105106
entry: string;
106107
wait: string;
108+
end: string;
107109
}) => {
108110
const response = await fetch(`${backendURL}edit-task`, {
109111
method: 'POST',
@@ -118,6 +120,7 @@ export const editTaskOnBackend = async ({
118120
start,
119121
entry,
120122
wait,
123+
end,
121124
}),
122125
headers: {
123126
'Content-Type': 'application/json',

0 commit comments

Comments
 (0)