feat(kanban): READ – hämta och rendera projekt‑tasks i tre kolumner
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Urban Modig
2025-10-15 11:45:49 +02:00
parent b99fc6d348
commit 9645a2e5eb
5 changed files with 122 additions and 2 deletions

34
src/features/tasks/api.ts Normal file
View File

@ -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<string, string | number | undefined>) {
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<any>()
// Stöd både ren lista och Page<T>
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,
})
}

View File

@ -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 (
<section aria-labelledby={`col-${status}`} className="flex-1 min-w-[260px]">
<header className="mb-2 flex items-center justify-between">
<h3 id={`col-${status}`} className="text-sm font-semibold uppercase tracking-wide opacity-70">
{title}
</h3>
<span className="text-xs opacity-60">{items.length}</span>
</header>
<div role="list" aria-describedby={`col-${status}`} className="space-y-2">
{items.map((t) => (
<TaskCard key={t.id} task={t} />
))}
</div>
</section>
)
}

View File

@ -0,0 +1,24 @@
import type { Task } from '@/types/task'
export function TaskCard({ task }: { task: Task }) {
return (
<div
role="listitem"
tabIndex={0}
className="rounded-xl border bg-white px-3 py-2 shadow-sm outline-offset-2 focus:outline focus:outline-2 focus:outline-indigo-500"
>
<div className="flex items-start justify-between gap-3">
<h4 className="text-sm font-medium leading-5">{task.title}</h4>
{task.priority && (
<span className="rounded-full border px-2 text-xs opacity-80">{task.priority}</span>
)}
</div>
{task.dueDate && (
<div className="mt-1 text-xs opacity-70">Due: {new Date(task.dueDate).toLocaleDateString()}</div>
)}
{task.assigneeName && (
<div className="mt-1 text-xs opacity-70">Assignee: {task.assigneeName}</div>
)}
</div>
)
}

View File

@ -1,2 +1,32 @@
// src/pages/ProjectBoardPage.tsx import { useParams } from 'react-router-dom'
export function ProjectBoardPage() { return <div>Project Kanban</div> } 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 <p>Laddar tasks</p>
if (isError) return <p>Något gick fel när tasks skulle hämtas.</p>
const tasks = data ?? []
const cols = splitByStatus(tasks)
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<KanbanColumn title="Open" status="OPEN" items={cols.OPEN ?? []} />
<KanbanColumn title="In progress" status="IN_PROGRESS" items={cols.IN_PROGRESS} />
<KanbanColumn title="Done" status="DONE" items={cols.DONE} />
</div>
)
}

12
src/types/task.ts Normal file
View File

@ -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
}