diff --git a/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java b/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java index 7a7258b..9afdf81 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java +++ b/backend/src/main/java/net/beetechgroup/beetask/entities/task/TaskHistoryItem.java @@ -4,10 +4,19 @@ import java.time.LocalDateTime; import net.beetechgroup.beetask.entities.User; public class TaskHistoryItem { + private Long id; private LocalDateTime startAt; private LocalDateTime endAt; private User user; + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + public void setEndAt(LocalDateTime endAt) { this.endAt = endAt; } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/TaskUseCaseConfig.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/TaskUseCaseConfig.java index c239792..7790dd0 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/TaskUseCaseConfig.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/config/TaskUseCaseConfig.java @@ -10,6 +10,8 @@ import net.beetechgroup.beetask.usecase.task.listall.ListAllTasksUseCase; import net.beetechgroup.beetask.usecase.task.start.StartTaskUseCase; import net.beetechgroup.beetask.usecase.task.stop.StopTaskUseCase; import net.beetechgroup.beetask.usecase.task.update.UpdateTaskStatusUseCase; +import net.beetechgroup.beetask.usecase.task.update.UpdateTaskUseCase; +import net.beetechgroup.beetask.usecase.task.get.GetTaskUseCase; @ApplicationScoped public class TaskUseCaseConfig { @@ -38,4 +40,14 @@ public class TaskUseCaseConfig { public UpdateTaskStatusUseCase updateTaskStatusUseCase(TaskRepository taskRepository) { return new UpdateTaskStatusUseCase(taskRepository); } + + @Produces + public UpdateTaskUseCase updateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository) { + return new UpdateTaskUseCase(taskRepository, projectRepository); + } + + @Produces + public GetTaskUseCase getTaskUseCase(TaskRepository taskRepository) { + return new GetTaskUseCase(taskRepository); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java index a6424b4..aa1f406 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/frameworks/persistence/mapper/TaskHistoryItemMapper.java @@ -6,6 +6,7 @@ import net.beetechgroup.beetask.frameworks.persistence.entities.TaskHistoryItemE public class TaskHistoryItemMapper { public static TaskHistoryItemEntity toEntity(TaskHistoryItem item) { TaskHistoryItemEntity entity = new TaskHistoryItemEntity(); + entity.setId(item.getId()); entity.setStartAt(item.getStartAt()); entity.setEndAt(item.getEndAt()); if (item.getUser() != null) { @@ -18,6 +19,7 @@ public class TaskHistoryItemMapper { if (entity == null) return null; TaskHistoryItem item = new TaskHistoryItem(); + item.setId(entity.getId()); item.setStartAt(entity.getStartAt()); item.setEndAt(entity.getEndAt()); if (entity.getUser() != null) { diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskRequest.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskRequest.java index b2c4bb3..5ad2956 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskRequest.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskRequest.java @@ -1,10 +1,13 @@ package net.beetechgroup.beetask.interfaceadapters.controllers.task; +import java.util.List; + import net.beetechgroup.beetask.entities.task.TaskStatus; public record CreateTaskRequest( String title, String description, TaskStatus status, - Long projectId) { + Long projectId, + List history) { } diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java index 7b6a65e..3e651a3 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/CreateTaskResponse.java @@ -16,6 +16,7 @@ public record CreateTaskResponse( } record TaskHistoryItemResponse( + Long id, LocalDateTime startAt, LocalDateTime endAt) { } diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java index f3ef7b8..36ccba1 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java @@ -27,6 +27,8 @@ import net.beetechgroup.beetask.usecase.task.listall.ListAllTasksUseCase; import net.beetechgroup.beetask.usecase.task.start.StartTaskUseCase; import net.beetechgroup.beetask.usecase.task.stop.StopTaskUseCase; import net.beetechgroup.beetask.usecase.task.update.UpdateTaskStatusUseCase; +import net.beetechgroup.beetask.usecase.task.update.UpdateTaskUseCase; +import net.beetechgroup.beetask.usecase.task.get.GetTaskUseCase; @Path("/tasks") @Produces(MediaType.APPLICATION_JSON) @@ -48,6 +50,12 @@ public class TaskController { @Inject UpdateTaskStatusUseCase updateTaskStatusUseCase; + + @Inject + UpdateTaskUseCase updateTaskUseCase; + + @Inject + GetTaskUseCase getTaskUseCase; @Inject SecurityIdentity securityIdentity; @@ -61,6 +69,23 @@ public class TaskController { return TaskControllerMapper.toCreateTaskResponse(output); } + @PUT + @Path("/{id}") + @Operation(summary = "Update a task", description = "Updates an existing task in the system") + @APIResponse(responseCode = "200", description = "Task updated successfully") + public CreateTaskResponse updateTask(@PathParam("id") Long id, CreateTaskRequest request) { + CreateTaskOutput output = updateTaskUseCase.execute(TaskControllerMapper.toUpdateTaskInput(id, request)); + return TaskControllerMapper.toCreateTaskResponse(output); + } + + @GET + @Path("/{id}") + @Operation(summary = "Get a task", description = "Gets a specific task by ID") + @APIResponse(responseCode = "200", description = "Task retrieved successfully") + public CreateTaskResponse getTask(@PathParam("id") Long id) { + return TaskControllerMapper.toCreateTaskResponse(getTaskUseCase.execute(id)); + } + @PUT @Path("/{id}/start") @Operation(summary = "Start a task", description = "Starts a task in the system") diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskControllerMapper.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskControllerMapper.java index 9333bdb..6b3a33f 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskControllerMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskControllerMapper.java @@ -4,6 +4,8 @@ import net.beetechgroup.beetask.usecase.task.create.CreateTaskInput; import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput; import net.beetechgroup.beetask.usecase.task.start.StartTaskInput; import net.beetechgroup.beetask.usecase.task.stop.StopTaskInput; +import net.beetechgroup.beetask.usecase.task.update.TaskHistoryItemInput; +import net.beetechgroup.beetask.usecase.task.update.UpdateTaskInput; public class TaskControllerMapper { @@ -24,7 +26,7 @@ public class TaskControllerMapper { output.status(), output.projectName(), output.finishedAt(), - output.history().stream().map(taskHistoryItemOutput -> new TaskHistoryItemResponse(taskHistoryItemOutput.startAt(), taskHistoryItemOutput.endAt())).toList() + output.history().stream().map(taskHistoryItemOutput -> new TaskHistoryItemResponse(taskHistoryItemOutput.id(), taskHistoryItemOutput.startAt(), taskHistoryItemOutput.endAt())).toList() ); } @@ -35,4 +37,17 @@ public class TaskControllerMapper { public static StopTaskInput toStopTaskInput(Long id) { return new StopTaskInput(id); } + + public static UpdateTaskInput toUpdateTaskInput(Long id, CreateTaskRequest request) { + return new UpdateTaskInput( + id, + request.title(), + request.description(), + request.status(), + request.projectId(), + request.history() != null ? request.history().stream() + .map(h -> new TaskHistoryItemInput(h.id(), h.startAt(), h.endAt())) + .toList() : null + ); + } } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java index c59eb45..b366238 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/CreateTaskMapper.java @@ -17,6 +17,7 @@ public class CreateTaskMapper { private static TaskHistoryItemOutput toTaskHistoryItemOutput(TaskHistoryItem taskHistoryItem) { return new TaskHistoryItemOutput( + taskHistoryItem.getId(), taskHistoryItem.getStartAt(), taskHistoryItem.getEndAt()); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/TaskHistoryItemOutput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/TaskHistoryItemOutput.java index b6af0de..ddbb822 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/TaskHistoryItemOutput.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/create/TaskHistoryItemOutput.java @@ -3,6 +3,7 @@ package net.beetechgroup.beetask.usecase.task.create; import java.time.LocalDateTime; public record TaskHistoryItemOutput( + Long id, LocalDateTime startAt, LocalDateTime endAt) { } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/get/GetTaskUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/get/GetTaskUseCase.java new file mode 100644 index 0000000..ef65a66 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/get/GetTaskUseCase.java @@ -0,0 +1,18 @@ +package net.beetechgroup.beetask.usecase.task.get; + +import net.beetechgroup.beetask.usecase.repository.TaskRepository; +import net.beetechgroup.beetask.usecase.task.create.CreateTaskMapper; +import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput; + +public class GetTaskUseCase { + private final TaskRepository taskRepository; + + public GetTaskUseCase(TaskRepository taskRepository) { + this.taskRepository = taskRepository; + } + + public CreateTaskOutput execute(Long id) { + //TODO deve validar se o usuario logado é o dono da task + return CreateTaskMapper.toCreateTaskOutput(taskRepository.findTaskById(id)); + } +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/TaskHistoryItemInput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/TaskHistoryItemInput.java new file mode 100644 index 0000000..cdf10c7 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/TaskHistoryItemInput.java @@ -0,0 +1,9 @@ +package net.beetechgroup.beetask.usecase.task.update; + +import java.time.LocalDateTime; + +public record TaskHistoryItemInput( + Long id, + LocalDateTime startAt, + LocalDateTime endAt) { +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskInput.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskInput.java new file mode 100644 index 0000000..49b8ab9 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskInput.java @@ -0,0 +1,13 @@ +package net.beetechgroup.beetask.usecase.task.update; + +import java.util.List; +import net.beetechgroup.beetask.entities.task.TaskStatus; + +public record UpdateTaskInput( + Long id, + String title, + String description, + TaskStatus status, + Long projectId, + List history) { +} diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java new file mode 100644 index 0000000..5f2d660 --- /dev/null +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/update/UpdateTaskUseCase.java @@ -0,0 +1,68 @@ +package net.beetechgroup.beetask.usecase.task.update; + +import java.util.List; +import net.beetechgroup.beetask.entities.Project; +import net.beetechgroup.beetask.entities.task.Task; +import net.beetechgroup.beetask.entities.task.TaskHistoryItem; +import net.beetechgroup.beetask.usecase.repository.ProjectRepository; +import net.beetechgroup.beetask.usecase.repository.TaskRepository; +import net.beetechgroup.beetask.usecase.task.create.CreateTaskMapper; +import net.beetechgroup.beetask.usecase.task.create.CreateTaskOutput; + +public class UpdateTaskUseCase { + private final TaskRepository taskRepository; + private final ProjectRepository projectRepository; + + public UpdateTaskUseCase(TaskRepository taskRepository, ProjectRepository projectRepository) { + this.taskRepository = taskRepository; + this.projectRepository = projectRepository; + } + + public CreateTaskOutput execute(UpdateTaskInput input) { + //TODO deve validar se o usuario logado é o dono da task + Task task = taskRepository.findTaskById(input.id()); + + task.setTitle(input.title()); + task.setDescription(input.description()); + task.setStatus(input.status()); + + if (input.projectId() != null) { + Project project = projectRepository.findProjectById(input.projectId()) + .orElseThrow(() -> new IllegalArgumentException("Projeto não encontrado com ID: " + input.projectId())); + task.setProject(project); + } else { + task.setProject(null); + } + + if (input.history() != null) { + List updatedHistory = input.history().stream().map(hInput -> { + if (hInput.id() != null) { + return task.getHistory().stream() + .filter(existing -> existing.getId().equals(hInput.id())) + .findFirst() + .map(existing -> { + existing.setStartAt(hInput.startAt()); + existing.setEndAt(hInput.endAt()); + return existing; + }) + .orElseGet(() -> { + TaskHistoryItem newItem = new TaskHistoryItem(); + newItem.setStartAt(hInput.startAt()); + newItem.setEndAt(hInput.endAt()); + return newItem; + }); + } else { + TaskHistoryItem newItem = new TaskHistoryItem(); + newItem.setStartAt(hInput.startAt()); + newItem.setEndAt(hInput.endAt()); + return newItem; + } + }).toList(); + + task.getHistory().clear(); + task.getHistory().addAll(updatedHistory); + } + + return CreateTaskMapper.toCreateTaskOutput(taskRepository.saveTask(task)); + } +} diff --git a/frontend/src/components/TaskTimer.tsx b/frontend/src/components/TaskTimer.tsx index 3318fc9..38a258a 100644 --- a/frontend/src/components/TaskTimer.tsx +++ b/frontend/src/components/TaskTimer.tsx @@ -71,7 +71,8 @@ export function TaskTimer({ task, onUpdate }: TaskTimerProps) { } }, [runningItem, accumulatedSeconds]) - async function handleStart() { + async function handleStart(e: React.MouseEvent) { + e.stopPropagation() setIsActionLoading(true) try { await taskService.startTask(task.id) @@ -83,7 +84,8 @@ export function TaskTimer({ task, onUpdate }: TaskTimerProps) { } } - async function handleStop() { + async function handleStop(e: React.MouseEvent) { + e.stopPropagation() setIsActionLoading(true) try { await taskService.stopTask(task.id) diff --git a/frontend/src/pages/CreateTaskPage.tsx b/frontend/src/pages/CreateTaskPage.tsx index 37b2e33..aa75f3e 100644 --- a/frontend/src/pages/CreateTaskPage.tsx +++ b/frontend/src/pages/CreateTaskPage.tsx @@ -2,30 +2,36 @@ import { motion } from "framer-motion"; import { ArrowLeft, CheckCircle2, + Clock, FileText, Layout, PlusCircle, Send, + XCircle, } from "lucide-react"; import { useState } from "react"; import type { ChangeEvent, FormEvent } from "react"; -import { Link } from "react-router-dom"; +import { Link, useNavigate, useParams } from "react-router-dom"; import { taskService } from "../services/taskService"; import { cn } from "../lib/utils"; import { TASK_STATUS_LABELS, TASK_STATUS_OPTIONS } from "../types/task"; -import type { CreateTaskPayload, TaskResponse } from "../types/task"; +import type { CreateTaskPayload, TaskResponse, TaskHistoryItem } from "../types/task"; import { useAuth } from "../contexts/AuthContext"; import { useEffect } from "react"; import { projectService, type Project } from "../services/projectService"; -type FormState = Omit & { projectId: string }; +type FormState = Omit & { + projectId: string; + history: TaskHistoryItem[]; +}; const initialFormState: FormState = { title: "", description: "", status: "NOT_STARTED", projectId: "", + history: [], }; export function CreateTaskPage() { @@ -35,6 +41,30 @@ export function CreateTaskPage() { const [createdTask, setCreatedTask] = useState(null); const [projects, setProjects] = useState([]); const { activeOrg } = useAuth(); + const { id } = useParams(); + const navigate = useNavigate(); + const isEdit = !!id; + + useEffect(() => { + if (isEdit) { + async function loadTask() { + try { + const data = await taskService.getTask(Number(id)); + setForm({ + title: data.title, + description: data.description || "", + status: data.status, + projectId: data.projectId?.toString() || "", + history: data.history || [], + }); + } catch (error) { + console.error("Erro ao carregar tarefa:", error); + setErrorMessage("Não foi possível carregar os dados da tarefa."); + } + } + loadTask(); + } + }, [id, isEdit]); useEffect(() => { async function loadProjects() { @@ -67,17 +97,22 @@ export function CreateTaskPage() { setIsSubmitting(true); try { - const payload: CreateTaskPayload = { + const payload = { title: form.title.trim(), description: form.description.trim(), status: form.status, projectId: form.projectId ? Number(form.projectId) : null, + history: form.history, }; - const response = await taskService.createTask(payload); - - setCreatedTask(response); - setForm(initialFormState); + if (isEdit) { + await taskService.updateTask(Number(id), payload); + navigate("/"); + } else { + const response = await taskService.createTask(payload); + setCreatedTask(response); + setForm(initialFormState); + } } catch (error) { setErrorMessage( error instanceof Error @@ -168,10 +203,10 @@ export function CreateTaskPage() {

- Nova Tarefa + {isEdit ? "Editar Tarefa" : "Nova Tarefa"}

- Preencha os detalhes abaixo para começar. + {isEdit ? `Editando tarefa #${id}` : "Preencha os detalhes abaixo para começar."}

@@ -254,6 +289,76 @@ export function CreateTaskPage() { className="w-full rounded-2xl border border-border-soft bg-app-bg px-5 py-4 text-sm font-medium outline-none transition-all focus:border-brand focus:ring-4 focus:ring-brand/10" /> + + {/* History Section */} +
+
+
+ + Histórico de Execução +
+ +
+ +
+ {form.history.map((item, index) => ( +
+
+ + { + const newHistory = [...form.history]; + newHistory[index] = { ...item, startAt: new Date(e.target.value).toISOString() }; + setForm(prev => ({ ...prev, history: newHistory })); + }} + className="w-full rounded-lg border border-border-soft bg-surface px-3 py-2 text-xs outline-none focus:border-brand" + /> +
+
+ + { + const newHistory = [...form.history]; + newHistory[index] = { ...item, endAt: e.target.value ? new Date(e.target.value).toISOString() : null }; + setForm(prev => ({ ...prev, history: newHistory })); + }} + className="w-full rounded-lg border border-border-soft bg-surface px-3 py-2 text-xs outline-none focus:border-brand" + /> +
+ +
+ ))} + {form.history.length === 0 && ( +

Nenhum registro de execução encontrado.

+ )} +
+
{errorMessage && ( @@ -282,11 +387,11 @@ export function CreateTaskPage() { )} > {isSubmitting ? ( - "Criando..." + isEdit ? "Salvando..." : "Criando..." ) : ( <> - Criar Tarefa + {isEdit ? "Salvar Alterações" : "Criar Tarefa"} )} diff --git a/frontend/src/pages/TaskBoardPage.tsx b/frontend/src/pages/TaskBoardPage.tsx index c99411b..eafbf64 100644 --- a/frontend/src/pages/TaskBoardPage.tsx +++ b/frontend/src/pages/TaskBoardPage.tsx @@ -12,7 +12,7 @@ import { XCircle, } from 'lucide-react' import { useEffect, useState } from 'react' -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { taskService } from '../services/taskService' import { cn } from '../lib/utils' @@ -50,6 +50,7 @@ import { TaskTimer } from '../components/TaskTimer' export function TaskBoardPage() { const [tasks, setTasks] = useState([]) const [isLoading, setIsLoading] = useState(true) + const navigate = useNavigate() const loadTasks = async (silent = false) => { if (!silent) setIsLoading(true) @@ -169,8 +170,9 @@ export function TaskBoardPage() { ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} + onClick={() => navigate(`/edit/${task.id}`)} className={cn( - 'group rounded-2xl border border-border-soft bg-surface p-4 shadow-sm transition-all', + 'group cursor-pointer rounded-2xl border border-border-soft bg-surface p-4 shadow-sm transition-all', snapshot.isDragging ? 'shadow-2xl ring-2 ring-brand/20' : 'hover:border-brand/30', )} > diff --git a/frontend/src/pages/TaskListPage.tsx b/frontend/src/pages/TaskListPage.tsx index 4ba21fc..3fe0672 100644 --- a/frontend/src/pages/TaskListPage.tsx +++ b/frontend/src/pages/TaskListPage.tsx @@ -13,7 +13,7 @@ import { XCircle, } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import { taskService } from '../services/taskService' import { cn } from '../lib/utils' @@ -55,6 +55,7 @@ export function TaskListPage() { const [tasks, setTasks] = useState([]) const [isLoading, setIsLoading] = useState(true) const [errorMessage, setErrorMessage] = useState(null) + const navigate = useNavigate() const [searchQuery, setSearchQuery] = useState('') const [statusFilter, setStatusFilter] = useState('ALL') @@ -178,7 +179,8 @@ export function TaskListPage() { exit={{ opacity: 0, scale: 0.95 }} transition={{ duration: 0.2, delay: i * 0.05 }} key={task.id} - className="group relative flex flex-col rounded-[2rem] border border-border-soft bg-surface p-6 shadow-sm transition-all hover:shadow-xl hover:shadow-brand/5" + onClick={() => navigate(`/edit/${task.id}`)} + className="group relative flex cursor-pointer flex-col rounded-[2rem] border border-border-soft bg-surface p-6 shadow-sm transition-all hover:shadow-xl hover:shadow-brand/5" >
, }, + { + path: "edit/:id", + element: , + }, { path: "board", element: , diff --git a/frontend/src/services/taskService.ts b/frontend/src/services/taskService.ts index e62ffd9..4ef8e6d 100644 --- a/frontend/src/services/taskService.ts +++ b/frontend/src/services/taskService.ts @@ -31,4 +31,15 @@ export const taskService = { method: 'PUT', }) }, + + async getTask(id: number): Promise { + return apiFetch(`/tasks/${id}`) + }, + + async updateTask(id: number, task: Partial): Promise { + return apiFetch(`/tasks/${id}`, { + method: 'PUT', + body: task, + }) + }, } diff --git a/frontend/src/types/task.ts b/frontend/src/types/task.ts index ef87146..aa212b1 100644 --- a/frontend/src/types/task.ts +++ b/frontend/src/types/task.ts @@ -22,6 +22,7 @@ export type CreateTaskPayload = { } export type TaskHistoryItem = { + id?: number startAt: string endAt: string | null }