* feat(code): add parseCodeNumber helper to lib/code.ts
Pure helper that extracts the trailing numeric sequence from a code string
(ST-007 → 7, T-42 → 42). Non-conforming codes fall back to Number.MAX_SAFE_INTEGER
so they sort to the end. Includes 5 unit tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(tasks): add code field to BacklogTask type and all task selects
Adds `code: string | null` to BacklogTask interface and includes it in
all Prisma task.findMany selects (backlog API, stories tasks API, page
hydration routes). Updates coerceTaskPayload and test fixtures to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(sort-order): derive story/task sort_order from parseCodeNumber(code)
All create paths (createStoryAction, saveTask, createTaskAction,
materializeIdeaPlanAction) and code-edit paths (updateStoryAction, saveTask
update) now set sort_order = parseCodeNumber(code) instead of last+1.
Removes stale last-record queries from create paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sort-order): decouple sprint membership actions from sort_order
createSprintAction and addStoryToSprintAction no longer write sort_order
when adding stories to a sprint. sort_order is derived from code via
parseCodeNumber, so membership should only set sprint_id + status.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(ordering): remove priority from all story/task orderBy
Story- en taak-ordering is nu puur sort_order asc (created_at als
tiebreaker). PBI-ordering (priority + sort_order) blijft ongewijzigd.
Gewijzigd: backlog/route, pbis/stories/route, claude-context/route,
next-story/route, workspace/route, tasks/route, sprint-runs (query +
in-memory sort), solo-workspace-server, page.tsx (app + mobile + sprint),
store compareStory, actions/sprints story-query, next-story test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(dnd): remove drag-and-drop reorder for stories and tasks
- Remove reorderStoriesAction, reorderTasksAction, reorderSprintStoriesAction
- Delete REST route app/api/stories/[id]/tasks/reorder/route.ts
- Remove DnD from backlog story-panel and task-panel (flat list)
- Remove reorder-within-sprint branch from sprint-board-client handleDragEnd
- Switch SortableSprintRow to plain SprintRow using useDraggable (membership drag kept)
- Remove all DnD from task-list (status toggle + edit kept)
- Remove story-order/task-order/sprint-story-order/sprint-task-order mutation types and store handlers
- Remove related tests for deleted reorder route; fix sprint store tests
* feat(backlog): toon code-badge op backlog-taakkaarten
Geeft code={task.code} door aan <BacklogCard> in TaskCard (task-panel.tsx).
BacklogCard rendert de CodeBadge al conditionally — alleen de prop ontbrak.
* feat(migration): backfill story/task sort_order from code numeric suffix
One-time Prisma migration that sets sort_order = trailing numeric part
of code for all existing stories and tasks, consistent with
parseCodeNumber (fallback = Number.MAX_SAFE_INTEGER for non-conforming
codes). PBIs are intentionally excluded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs+tests(sort-order): update for code-binding order on stories/tasks
- Rewrite docs/patterns/sort-order.md: float-insertion PBI only; story/task
sort_order = parseCodeNumber(code), never drag/membership mutated
- Update plan-to-pbi-flow.md: sort_order auto, sprint_id param, priority=label
- Update make-plan.md: priority=label, array order = execution order
- Update rest-contract.md: fix sprint-tasks ordering, remove reorder endpoint
- Add ADR-0011: code is bindende volgordesleutel voor stories/taken
- Regenerate docs/INDEX.md via npm run docs
- Remove reorderStoriesAction/reorderTasksAction mocks from backlog tests
- Remove dnd-kit mocks from task-panel test (panel no longer uses dnd)
- Extend materializeIdeaPlanAction test: assert sort_order=parseCodeNumber(code)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
137 lines
3.9 KiB
TypeScript
137 lines
3.9 KiB
TypeScript
'use client'
|
|
|
|
import { useRouter } from 'next/navigation'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import { PanelNavBar } from '@/components/shared/panel-nav-bar'
|
|
import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
|
import { useShallow } from 'zustand/react/shallow'
|
|
import { useProductWorkspaceStore } from '@/stores/product-workspace/store'
|
|
import { selectTasksForActiveStory } from '@/stores/product-workspace/selectors'
|
|
import type {
|
|
BacklogTask,
|
|
TaskDetail,
|
|
} from '@/stores/product-workspace/types'
|
|
import { BacklogCard } from './backlog-card'
|
|
import { debugProps } from '@/lib/debug'
|
|
import { EmptyPanel } from './empty-panel'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
const STATUS_COLORS: Record<string, string> = {
|
|
TO_DO: 'bg-status-todo/15 text-status-todo border-status-todo/30',
|
|
IN_PROGRESS: 'bg-status-in-progress/15 text-status-in-progress border-status-in-progress/30',
|
|
REVIEW: 'bg-status-review/15 text-status-review border-status-review/30',
|
|
DONE: 'bg-status-done/15 text-status-done border-status-done/30',
|
|
}
|
|
const STATUS_LABELS: Record<string, string> = {
|
|
TO_DO: 'To Do',
|
|
IN_PROGRESS: 'Bezig',
|
|
REVIEW: 'Review',
|
|
DONE: 'Klaar',
|
|
}
|
|
|
|
function TaskCard({
|
|
task,
|
|
onClick,
|
|
}: {
|
|
task: BacklogTask | TaskDetail
|
|
onClick: () => void
|
|
}) {
|
|
return (
|
|
<BacklogCard
|
|
title={task.title}
|
|
priority={task.priority}
|
|
code={task.code}
|
|
onClick={onClick}
|
|
badge={
|
|
<Badge
|
|
className={cn(
|
|
'text-[10px] px-1.5 py-0 border',
|
|
STATUS_COLORS[task.status] ?? STATUS_COLORS.TO_DO,
|
|
)}
|
|
>
|
|
{STATUS_LABELS[task.status] ?? task.status}
|
|
</Badge>
|
|
}
|
|
/>
|
|
)
|
|
}
|
|
|
|
interface TaskPanelProps {
|
|
productId: string
|
|
isDemo: boolean
|
|
closePath: string
|
|
}
|
|
|
|
// PBI-74 / T-851: leest tasks voor active story via selectTasksForActiveStory.
|
|
export function TaskPanel({ isDemo, closePath }: TaskPanelProps) {
|
|
const router = useRouter()
|
|
const selectedStoryId = useProductWorkspaceStore((s) => s.context.activeStoryId)
|
|
const rawTasks = useProductWorkspaceStore(useShallow(selectTasksForActiveStory)) as
|
|
| (BacklogTask | TaskDetail)[]
|
|
|
|
const tasks: (BacklogTask | TaskDetail)[] | null = selectedStoryId
|
|
? rawTasks
|
|
: null
|
|
|
|
const navActions = (
|
|
<DemoTooltip show={isDemo}>
|
|
<Button
|
|
size="sm"
|
|
className="h-7 text-xs"
|
|
disabled={isDemo || !selectedStoryId}
|
|
onClick={() => {
|
|
if (!selectedStoryId) return
|
|
router.push(`${closePath}?newTask=1&storyId=${selectedStoryId}`)
|
|
}}
|
|
{...debugProps('task-panel__actions')}
|
|
>
|
|
+ Nieuwe taak
|
|
</Button>
|
|
</DemoTooltip>
|
|
)
|
|
|
|
const dp = debugProps('task-panel', 'TaskPanel', 'components/backlog/task-panel.tsx')
|
|
|
|
if (tasks === null) {
|
|
return (
|
|
<div className="flex flex-col h-full" {...dp}>
|
|
<PanelNavBar title="Taken" actions={navActions} />
|
|
<EmptyPanel message="Selecteer een story om de taken te bekijken." />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (tasks.length === 0) {
|
|
return (
|
|
<div className="flex flex-col h-full" {...dp}>
|
|
<PanelNavBar title="Taken" actions={navActions} />
|
|
<EmptyPanel
|
|
message="Nog geen taken voor deze story."
|
|
action={{
|
|
label: 'Nieuwe taak',
|
|
onClick: () => router.push(`${closePath}?newTask=1&storyId=${selectedStoryId}`),
|
|
disabled: isDemo,
|
|
}}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col h-full" {...dp}>
|
|
<PanelNavBar title="Taken" actions={navActions} />
|
|
<div className="flex-1 overflow-y-auto p-3">
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{tasks.map((task) => (
|
|
<TaskCard
|
|
key={task.id}
|
|
task={task}
|
|
onClick={() => router.push(`${closePath}?editTask=${task.id}`)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|