Skip to content

Commit 129238d

Browse files
committed
feat(task): task integration
1 parent a320d16 commit 129238d

5 files changed

Lines changed: 59 additions & 54 deletions

File tree

internal/tasks/features/update_task/v1/commands/update_task.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func (dto *UpdateTask) Validate() error {
2424
return validation.ValidateStruct(dto,
2525
validation.Field(&dto.ID, validation.Required),
2626
validation.Field(&dto.Event, validation.Required, validation.In(
27-
statemachine.TaskEventStart,
2827
statemachine.TaskEventComplete,
2928
statemachine.TaskEventCancel,
3029
).Error("Invalid transition or event not found")),

internal/tasks/features/update_task/v1/commands/update_task_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (suite *testUpdateTask) SetupSuite() {
5252
suite.userID = user.ID
5353

5454
// Get task
55-
task, err := getTaskInProgress(context.Background(), suite.db.Db)
55+
task, err := getTask(context.Background(), suite.db.Db)
5656
suite.NoError(err)
5757
suite.taskID = task.ID
5858

@@ -66,40 +66,40 @@ func TestUpdateTaskSuite(t *testing.T) {
6666
func (suite *testUpdateTask) TestUpdateTaskHandler_Handle() {
6767
ctx := context.Background()
6868

69-
suite.Run("Successful transition from Not Started to In Progress", func() {
69+
suite.Run("Successful transition from Not Started to Completed", func() {
7070
// Setup
7171
_, err := suite.db.Db.Exec(ctx, `UPDATE tasks SET status = $1, notes = NULL, cancelled_at = NULL WHERE id = $2`,
7272
string(gen.TaskStatusEnumNotStarted), suite.taskID)
7373
suite.NoError(err)
7474

7575
resp, err := suite.handler.Handle(ctx, &UpdateTask{
7676
ID: suite.taskID,
77-
Event: statemachine.TaskEventStart,
77+
Event: statemachine.TaskEventComplete,
7878
})
7979
suite.NoError(err)
8080
suite.NotNil(resp)
8181

8282
// Check stored task
8383
task, err := suite.dal.GetTask(ctx, suite.db.Db, resp.ID)
8484
suite.NoError(err)
85-
suite.Equal(string(gen.TaskStatusEnumInProgress), string(task.Status))
85+
suite.Equal(string(gen.TaskStatusEnumCompleted), string(task.Status))
8686
})
8787

88-
suite.Run("Error: invalid event from not_started to completed", func() {
88+
suite.Run("Error: invalid event from completed to not_started", func() {
8989
// Setup
9090
_, err := suite.db.Db.Exec(ctx, `UPDATE tasks SET status = $1, notes = NULL, cancelled_at = NULL WHERE id = $2`,
91-
string(gen.TaskStatusEnumNotStarted), suite.taskID)
91+
string(gen.TaskStatusEnumCompleted), suite.taskID)
9292
suite.NoError(err)
9393

9494
resp, err := suite.handler.Handle(ctx, &UpdateTask{
9595
ID: suite.taskID,
96-
Event: statemachine.TaskEventComplete,
96+
Event: statemachine.TaskEventStart,
9797
})
9898
suite.Error(err)
9999
appErr, ok := err.(*apperrs.AppError)
100100
suite.True(ok, "Error should be an AppError")
101101
suite.Equal(
102-
"invalid event complete for state not_started",
102+
"invalid event start for state completed",
103103
appErr.Error(),
104104
)
105105
suite.Nil(resp)
@@ -108,7 +108,7 @@ func (suite *testUpdateTask) TestUpdateTaskHandler_Handle() {
108108
suite.Run("Error: missing notes for cancel event", func() {
109109
// Setup
110110
_, err := suite.db.Db.Exec(ctx, `UPDATE tasks SET status = $1, notes = NULL, cancelled_at = NULL WHERE id = $2`,
111-
string(gen.TaskStatusEnumInProgress), suite.taskID)
111+
string(gen.TaskStatusEnumNotStarted), suite.taskID)
112112
suite.NoError(err)
113113

114114
resp, err := suite.handler.Handle(ctx, &UpdateTask{
@@ -127,7 +127,7 @@ func (suite *testUpdateTask) TestUpdateTaskHandler_Handle() {
127127
suite.Run("Concurrency: Multiple simultaneous updates", func() {
128128
// Setup: Set task to in_progress state
129129
_, err := suite.db.Db.Exec(ctx, `UPDATE tasks SET status = $1, notes = NULL, cancelled_at = NULL WHERE id = $2`,
130-
string(gen.TaskStatusEnumInProgress), suite.taskID)
130+
string(gen.TaskStatusEnumNotStarted), suite.taskID)
131131
suite.NoError(err)
132132

133133
const numGoroutines = 10
@@ -197,11 +197,11 @@ type getTaskRow struct {
197197
}
198198

199199
// GetTaskInProgress retrieves one task with status 'in_progress' using pgxpool.Pool
200-
func getTaskInProgress(ctx context.Context, db postgres.DBTX) (*getTaskRow, error) {
200+
func getTask(ctx context.Context, db postgres.DBTX) (*getTaskRow, error) {
201201
query := `
202202
SELECT id, schedule_id, name, description, status, completed_at, cancelled_at, notes, created_at, updated_at
203203
FROM tasks
204-
WHERE status = 'in_progress'
204+
WHERE status = 'not_started'
205205
LIMIT 1
206206
`
207207
var task getTaskRow

internal/tasks/state_machine/task.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import "fmt"
55
// Task states
66
const (
77
StateNotStarted = "not_started"
8-
StateInProgress = "in_progress"
98
StateCompleted = "completed"
109
StateCancelled = "cancelled"
1110
)
@@ -32,10 +31,6 @@ type TransitionCallback func(task *Task, fromState, toState string) error
3231
// TaskEventMap defines valid state transitions
3332
var TaskEventMap = map[string]map[string]string{
3433
StateNotStarted: {
35-
TaskEventStart: StateInProgress,
36-
TaskEventCancel: StateCancelled,
37-
},
38-
StateInProgress: {
3934
TaskEventComplete: StateCompleted,
4035
TaskEventCancel: StateCancelled,
4136
},

schema/postgres/migrations/20250918055317_seed.sql

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,46 +28,46 @@ VALUES('sch_dnozJ1bNjOqbZbVP5', 'user_iRFGnwaKD3QHH2mV', 'Client Presentation',
2828
-- insert tasks
2929
INSERT INTO tasks
3030
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
31-
VALUES('task_hyIaQQMQVmjmM3DA', 'sch_Qt5PyQsSqmMFiZFR8', 'Prepare Agenda', 'Draft and share meeting agenda with team', 'completed'::public.task_status_enum, '2025-09-24 15:00:00.000', NULL, 'Agenda shared via email', '2025-09-20 16:30:00.000', '2025-09-24 15:00:00.000', NULL);
31+
VALUES('task_hyIaQQMQVmjmM3DA', 'sch_Qt5PyQsSqmMFiZFR8', 'Prepare Agenda', 'Draft and share meeting agenda with team', 'not_started'::public.task_status_enum, '2025-09-24 15:00:00.000', NULL, 'Agenda shared via email', clock_timestamp(), NULL, NULL);
3232
INSERT INTO tasks
3333
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
34-
VALUES('task_QtWVJdpawoPVq0jK', 'sch_Qt5PyQsSqmMFiZFR8', 'Lead Discussion', 'Facilitate team discussion on project updates', 'not_started'::public.task_status_enum, NULL, NULL, NULL, '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
34+
VALUES('task_QtWVJdpawoPVq0jK', 'sch_Qt5PyQsSqmMFiZFR8', 'Lead Discussion', 'Facilitate team discussion on project updates', 'not_started'::public.task_status_enum, NULL, NULL, NULL, clock_timestamp(), NULL, NULL);
3535
INSERT INTO tasks
3636
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
37-
VALUES('task_pg693qFgE6sfLZ0m', 'sch_zbM4UwQ1M45Fo02Ij', 'Review Sprint Progress', 'Review completed tasks from current sprint and update progress in project management tool', 'completed'::public.task_status_enum, '2025-09-15 09:15:00.000', NULL, 'Sprint is 80% complete. All major features are done, working on bug fixes.', '2025-09-15 09:00:00.000', '2025-09-15 09:15:00.000', NULL);
37+
VALUES('task_pg693qFgE6sfLZ0m', 'sch_zbM4UwQ1M45Fo02Ij', 'Review Sprint Progress', 'Review completed tasks from current sprint and update progress in project management tool', 'not_started'::public.task_status_enum, '2025-09-15 09:15:00.000', NULL, 'Sprint is 80% complete. All major features are done, working on bug fixes.', clock_timestamp(), NULL, NULL);
3838
INSERT INTO tasks
3939
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
40-
VALUES('task_xSIPMGslgflVpmyB', 'sch_zbM4UwQ1M45Fo02Ij', 'Discuss Blockers', 'Identify and discuss any blockers or impediments affecting team productivity', 'completed'::public.task_status_enum, '2025-09-15 09:30:00.000', NULL, 'Database performance issue resolved. Waiting for QA feedback on feature X.', '2025-09-15 09:15:00.000', '2025-09-15 09:30:00.000', NULL);
40+
VALUES('task_xSIPMGslgflVpmyB', 'sch_zbM4UwQ1M45Fo02Ij', 'Discuss Blockers', 'Identify and discuss any blockers or impediments affecting team productivity', 'not_started'::public.task_status_enum, '2025-09-15 09:30:00.000', NULL, 'Database performance issue resolved. Waiting for QA feedback on feature X.', clock_timestamp(), NULL, NULL);
4141
INSERT INTO tasks
4242
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
43-
VALUES('task_8vHUD2byDpWv6tJc', 'sch_zbM4UwQ1M45Fo02Ij', 'Plan Upcoming Tasks', 'Plan and assign tasks for the upcoming week based on sprint goals', 'completed'::public.task_status_enum, '2025-09-15 09:45:00.000', NULL, 'Assigned 3 new tasks. Focus on testing and deployment preparation.', '2025-09-15 09:30:00.000', '2025-09-15 09:45:00.000', NULL);
43+
VALUES('task_8vHUD2byDpWv6tJc', 'sch_zbM4UwQ1M45Fo02Ij', 'Plan Upcoming Tasks', 'Plan and assign tasks for the upcoming week based on sprint goals', 'not_started'::public.task_status_enum, '2025-09-15 09:45:00.000', NULL, 'Assigned 3 new tasks. Focus on testing and deployment preparation.', clock_timestamp(), NULL, NULL);
4444
INSERT INTO tasks
4545
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
46-
VALUES('task_jRbwXok12O7fflPT', 'sch_zbM4UwQ1M45Fo02Ij', 'Team Sync Updates', 'Share individual updates and coordinate cross-team dependencies', 'completed'::public.task_status_enum, '2025-09-15 10:00:00.000', NULL, 'Good alignment across teams. Marketing needs API docs by Friday.', '2025-09-15 09:45:00.000', '2025-09-15 10:00:00.000', NULL);
46+
VALUES('task_jRbwXok12O7fflPT', 'sch_zbM4UwQ1M45Fo02Ij', 'Team Sync Updates', 'Share individual updates and coordinate cross-team dependencies', 'not_started'::public.task_status_enum, '2025-09-15 10:00:00.000', NULL, 'Good alignment across teams. Marketing needs API docs by Friday.', clock_timestamp(), NULL, NULL);
4747
INSERT INTO tasks
4848
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
49-
VALUES('task_dJRVrw4hgqxiIKiM', 'sch_usw06crHj0wcCyaqI', 'Check Vitals', 'Nurse to check blood pressure, temperature, and weight before consultation', 'not_started'::public.task_status_enum, NULL, NULL, NULL, '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
49+
VALUES('task_dJRVrw4hgqxiIKiM', 'sch_usw06crHj0wcCyaqI', 'Check Vitals', 'Nurse to check blood pressure, temperature, and weight before consultation', 'not_started'::public.task_status_enum, NULL, NULL, NULL, clock_timestamp(), NULL, NULL);
5050
INSERT INTO tasks
5151
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
52-
VALUES('task_LUOVv5W0re4WZoWQ', 'sch_usw06crHj0wcCyaqI', 'Doctor Consultation', 'Meet with Dr. Smith to review lab results and overall health check', 'not_started'::public.task_status_enum, NULL, NULL, NULL, '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
52+
VALUES('task_LUOVv5W0re4WZoWQ', 'sch_usw06crHj0wcCyaqI', 'Doctor Consultation', 'Meet with Dr. Smith to review lab results and overall health check', 'not_started'::public.task_status_enum, NULL, NULL, NULL, clock_timestamp(), NULL, NULL);
5353
INSERT INTO tasks
5454
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
55-
VALUES('task_ud1v5JmVucpAIFe4', 'sch_8CvlFa8rc8CNBH1Y7', 'Prepare Materials', 'Create presentation slides and project timeline', 'not_started'::public.task_status_enum, NULL, NULL, NULL, '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
55+
VALUES('task_ud1v5JmVucpAIFe4', 'sch_8CvlFa8rc8CNBH1Y7', 'Prepare Materials', 'Create presentation slides and project timeline', 'not_started'::public.task_status_enum, NULL, NULL, NULL, clock_timestamp(), NULL, NULL);
5656
INSERT INTO tasks
5757
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
58-
VALUES('task_fknkE01GjhU0gszt', 'sch_8CvlFa8rc8CNBH1Y7', 'Invite Stakeholders', 'Send meeting invites to all project stakeholders', 'not_started'::public.task_status_enum, NULL, NULL, NULL, '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
58+
VALUES('task_fknkE01GjhU0gszt', 'sch_8CvlFa8rc8CNBH1Y7', 'Invite Stakeholders', 'Send meeting invites to all project stakeholders', 'not_started'::public.task_status_enum, NULL, NULL, NULL, clock_timestamp(), NULL, NULL);
5959
INSERT INTO tasks
6060
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
61-
VALUES('task_7CmwlIVi4xkMXELG', 'sch_dnozJ1bNjOqbZbVP5', 'Update Slides', 'Revise presentation slides based on latest project data', 'in_progress'::public.task_status_enum, NULL, NULL, 'Waiting for final data from analytics team', '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
61+
VALUES('task_7CmwlIVi4xkMXELG', 'sch_dnozJ1bNjOqbZbVP5', 'Update Slides', 'Revise presentation slides based on latest project data', 'not_started'::public.task_status_enum, NULL, NULL, 'Waiting for final data from analytics team', clock_timestamp(), NULL, NULL);
6262
INSERT INTO tasks
6363
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
64-
VALUES('task_AGWZeaii400APziR', 'sch_dnozJ1bNjOqbZbVP5', 'Practice Presentation', 'Rehearse presentation delivery with team', 'not_started'::public.task_status_enum, NULL, NULL, NULL, '2025-09-20 16:30:00.000', '2025-09-20 16:30:00.000', NULL);
64+
VALUES('task_AGWZeaii400APziR', 'sch_dnozJ1bNjOqbZbVP5', 'Practice Presentation', 'Rehearse presentation delivery with team', 'not_started'::public.task_status_enum, NULL, NULL, NULL, clock_timestamp(), NULL, NULL);
6565
INSERT INTO tasks
6666
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
67-
VALUES('task_v2OUcWgYBAXmR0S6', 'sch_Lj6vYOxtJf81G8PgG', 'Reserve Venue', 'Book conference room for workshop', 'cancelled'::public.task_status_enum, NULL, '2025-09-18 08:00:00.000', 'Cancelled due to low attendance', '2025-09-17 16:00:00.000', '2025-09-18 08:00:00.000', NULL);
67+
VALUES('task_v2OUcWgYBAXmR0S6', 'sch_Lj6vYOxtJf81G8PgG', 'Reserve Venue', 'Book conference room for workshop', 'cancelled'::public.task_status_enum, NULL, '2025-09-18 08:00:00.000', 'Cancelled due to low attendance', clock_timestamp(), NULL, NULL);
6868
INSERT INTO tasks
6969
(id, schedule_id, "name", description, status, completed_at, cancelled_at, notes, created_at, updated_at, deleted_at)
70-
VALUES('task_j92ypRhLifhgw0B8', 'sch_Lj6vYOxtJf81G8PgG', 'Prepare Materials', 'Develop workshop handouts and slides', 'cancelled'::public.task_status_enum, NULL, '2025-09-18 08:00:00.000', 'Cancelled due to low attendance', '2025-09-17 16:00:00.000', '2025-09-18 08:00:00.000', NULL);
70+
VALUES('task_j92ypRhLifhgw0B8', 'sch_Lj6vYOxtJf81G8PgG', 'Prepare Materials', 'Develop workshop handouts and slides', 'cancelled'::public.task_status_enum, NULL, '2025-09-18 08:00:00.000', 'Cancelled due to low attendance', clock_timestamp(), NULL, NULL);
7171

7272
-- +goose StatementEnd
7373

ui/app/schedule/[schedule_id]/occurrence/[occurrence_id]/page.tsx

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input";
66
import { Dialog, DialogContent } from "@/components/ui/dialog";
77
import { ArrowLeft, Calendar, Clock, Mail, Phone, MapPin, Check, X } from "lucide-react";
88
import Link from "next/link";
9-
import { useEffect, useState } from "react";
9+
import { useCallback, useEffect, useState } from "react";
1010
import { useRouter, useParams } from "next/navigation";
1111
import { useSnapshot } from "valtio";
1212
import { userStore, scheduleStore } from "@/store";
@@ -18,6 +18,8 @@ import CancelCheckInButton from "@/components/schedules/cancel-checkin-btn";
1818
import CheckOutButton from "@/components/schedules/checkout-btn";
1919
import CompletionDialog from "@/components/schedules/completion-dialog";
2020
import { Schedule } from "@/types/models/schedules";
21+
import { updateTaskProgress } from "@/apis/tasks";
22+
import { UpdateTaskProgressRequest } from "@/types/models/tasks";
2123

2224
export default function ScheduleDetails() {
2325
const router = useRouter();
@@ -28,24 +30,26 @@ export default function ScheduleDetails() {
2830
const userSnap = useSnapshot(userStore);
2931
const scheduleSnap = useSnapshot(scheduleStore);
3032
const [selectedSchedule, setSelectedSchedule] = useState<Schedule | null>(null);
33+
const [reason, setReason] = useState<string>("");
3134

32-
useEffect(() => {
33-
const loadData = async () => {
34-
try {
35-
scheduleStore.setLoading(true);
35+
const loadData = useCallback(async () => {
36+
try {
37+
scheduleStore.setLoading(true);
3638

37-
const schedule = await getScheduleById(scheduleId, occurrenceId);
38-
scheduleStore.setSchedule(schedule.data!);
39-
setSelectedSchedule(schedule.data!);
40-
} catch (error) {
41-
console.error("Error loading schedule:", error);
42-
} finally {
43-
scheduleStore.setLoading(false);
44-
}
45-
};
46-
loadData();
39+
const schedule = await getScheduleById(scheduleId, occurrenceId);
40+
scheduleStore.setSchedule(schedule.data!);
41+
setSelectedSchedule(schedule.data!);
42+
} catch (error) {
43+
console.error("Error loading schedule:", error);
44+
} finally {
45+
scheduleStore.setLoading(false);
46+
}
4747
}, [scheduleId, occurrenceId]);
4848

49+
useEffect(() => {
50+
loadData();
51+
}, [loadData]);
52+
4953
const [showCompletionDialog, setShowCompletionDialog] = useState(false);
5054

5155
if (!scheduleSnap.schedule) {
@@ -63,12 +67,21 @@ export default function ScheduleDetails() {
6367
);
6468
}
6569

66-
const handleTaskAction = (taskId: string, action: "completed" | "cancelled") => {
67-
// scheduleStore.updateTaskStatus(scheduleId, taskId, action);
70+
const handleTaskAction = async (taskId: string, action: "complete" | "cancel") => {
71+
try {
72+
const response = await updateTaskProgress(taskId, {
73+
event: action,
74+
notes: action === "cancel" ? reason : "",
75+
});
76+
await loadData();
77+
} catch (error) {
78+
console.error(error);
79+
window.alert("An error occurred while updating the task progress.");
80+
}
6881
};
6982

7083
const handleReasonChange = (taskId: string, reason: string) => {
71-
// scheduleStore.updateTaskStatus(scheduleId, taskId, "cancelled", reason);
84+
setReason(reason);
7285
};
7386

7487
const handleClockOut = () => {
@@ -205,7 +218,7 @@ export default function ScheduleDetails() {
205218
<div className="flex items-center gap-4 mb-3">
206219
<button
207220
onClick={() =>
208-
handleTaskAction(task.id, "completed")
221+
handleTaskAction(task.id, "complete")
209222
}
210223
className={`flex items-center gap-2 px-3 py-1 rounded text-sm font-medium transition-colors cursor-pointer ${
211224
task.status === "completed"
@@ -220,9 +233,7 @@ export default function ScheduleDetails() {
220233
<span className="text-gray-400">|</span>
221234

222235
<button
223-
onClick={() =>
224-
handleTaskAction(task.id, "cancelled")
225-
}
236+
onClick={() => handleTaskAction(task.id, "cancel")}
226237
className={`flex items-center gap-2 px-3 py-1 rounded text-sm font-medium transition-colors cursor-pointer ${
227238
task.status === "cancelled"
228239
? "bg-red-100 text-red-700 border border-red-300"

0 commit comments

Comments
 (0)