diff --git a/src/features/tasks/api.ts b/src/features/tasks/api.ts new file mode 100644 index 0000000..f81f766 --- /dev/null +++ b/src/features/tasks/api.ts @@ -0,0 +1,34 @@ +import { api } from '@/lib/http' +import { useQuery } from '@tanstack/react-query' +import type { Task, TaskStatus } from '@/types/task' + +export function projectTasksKey(projectId: string, params?: Record) { + return ['projectTasks', projectId, params] as const +} + +export async function fetchProjectTasks(projectId: string, params?: { status?: TaskStatus }) { + const search = new URLSearchParams() + if (params?.status) search.set('status', params.status) + + const url = `api/v1/projects/${projectId}/tasks${search.size ? `?${search.toString()}` : ''}` + + // Läs råsvaret och normalisera till Task[] + const raw = await api.get(url).json() + + // Stöd både ren lista och Page + const list: unknown = + Array.isArray(raw) ? raw + : Array.isArray(raw?.content) ? raw.content + : Array.isArray(raw?.items) ? raw.items + : [] + + return list as Task[] +} + +export function useProjectTasksQuery(projectId: string) { + return useQuery({ + queryKey: projectTasksKey(projectId), + queryFn: () => fetchProjectTasks(projectId), + staleTime: 10_000, + }) +} diff --git a/src/features/tasks/components/KanbanColumn.tsx b/src/features/tasks/components/KanbanColumn.tsx new file mode 100644 index 0000000..13d2855 --- /dev/null +++ b/src/features/tasks/components/KanbanColumn.tsx @@ -0,0 +1,20 @@ +import type { Task, TaskStatus } from '@/types/task' +import { TaskCard } from './TaskCard' + +export function KanbanColumn({ title, status, items }: { title: string; status: TaskStatus; items: Task[] }) { + return ( +
+
+

+ {title} +

+ {items.length} +
+
+ {items.map((t) => ( + + ))} +
+
+ ) +} \ No newline at end of file diff --git a/src/features/tasks/components/TaskCard.tsx b/src/features/tasks/components/TaskCard.tsx new file mode 100644 index 0000000..8bdb562 --- /dev/null +++ b/src/features/tasks/components/TaskCard.tsx @@ -0,0 +1,24 @@ +import type { Task } from '@/types/task' + +export function TaskCard({ task }: { task: Task }) { + return ( +
+
+

{task.title}

+ {task.priority && ( + {task.priority} + )} +
+ {task.dueDate && ( +
Due: {new Date(task.dueDate).toLocaleDateString()}
+ )} + {task.assigneeName && ( +
Assignee: {task.assigneeName}
+ )} +
+ ) +} \ No newline at end of file diff --git a/src/pages/ProjectBoardPage.tsx b/src/pages/ProjectBoardPage.tsx index d08a332..efd7584 100644 --- a/src/pages/ProjectBoardPage.tsx +++ b/src/pages/ProjectBoardPage.tsx @@ -1,2 +1,32 @@ -// src/pages/ProjectBoardPage.tsx -export function ProjectBoardPage() { return
Project Kanban
} +import { useParams } from 'react-router-dom' +import { useProjectTasksQuery } from '@/features/tasks/api' +import type { Task } from '@/types/task' +import { KanbanColumn } from '@/features/tasks/components/KanbanColumn' + +function splitByStatus(items: Task[] | unknown) { + const arr = Array.isArray(items) ? items as Task[] : [] + return { + OPEN: arr.filter(t => t.status === 'OPEN'), + IN_PROGRESS: arr.filter(t => t.status === 'IN_PROGRESS'), + DONE: arr.filter(t => t.status === 'DONE'), + } +} + +export function ProjectBoardPage() { + const { projectId = '' } = useParams() + const { data, isLoading, isError } = useProjectTasksQuery(projectId) + + if (isLoading) return

Laddar tasks…

+ if (isError) return

Något gick fel när tasks skulle hämtas.

+ + const tasks = data ?? [] + const cols = splitByStatus(tasks) + + return ( +
+ + + +
+ ) +} \ No newline at end of file diff --git a/src/types/task.ts b/src/types/task.ts new file mode 100644 index 0000000..e48d8ca --- /dev/null +++ b/src/types/task.ts @@ -0,0 +1,12 @@ +export type TaskStatus = 'OPEN' | 'IN_PROGRESS' | 'DONE' +export type TaskPriority = 'LOW' | 'MEDIUM' | 'HIGH' + +export interface Task { + id: string + title: string + description?: string | null + status: TaskStatus + priority?: TaskPriority | null + dueDate?: string | null // ISO + assigneeName?: string | null +} \ No newline at end of file