feat(codes): UI toont en accepteert code voor taken
- TaskDialog: code-input boven titel (font-mono, optional, placeholder "auto"), CodeBadge in dialog header bij edit - EditTaskLoader: select code uit DB voor de dialog - Solo page: vervang inline deriveTaskCode-logica door directe task.code uit DB - Sprint page + TaskList + SprintBoardClient: Task-type krijgt verplicht code-veld; TaskList laat ongebruikte storyCode prop vallen omdat code-derivatie niet meer nodig is Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7c82a736f5
commit
081a0a51c3
6 changed files with 52 additions and 29 deletions
|
|
@ -72,26 +72,21 @@ export default async function SoloProductPage({ params }: Props) {
|
|||
}),
|
||||
])
|
||||
|
||||
const tasks: SoloTask[] = rawTasks.map(t => {
|
||||
const positionInStory = t.story.tasks.findIndex(st => st.id === t.id)
|
||||
const taskCode =
|
||||
t.story.code && positionInStory >= 0 ? `${t.story.code}.${positionInStory + 1}` : null
|
||||
return {
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
implementation_plan: t.implementation_plan,
|
||||
priority: t.priority,
|
||||
sort_order: t.sort_order,
|
||||
status: t.status as SoloTask['status'],
|
||||
verify_only: t.verify_only,
|
||||
verify_required: t.verify_required as SoloTask['verify_required'],
|
||||
story_id: t.story.id,
|
||||
story_code: t.story.code,
|
||||
story_title: t.story.title,
|
||||
task_code: taskCode,
|
||||
}
|
||||
})
|
||||
const tasks: SoloTask[] = rawTasks.map(t => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
implementation_plan: t.implementation_plan,
|
||||
priority: t.priority,
|
||||
sort_order: t.sort_order,
|
||||
status: t.status as SoloTask['status'],
|
||||
verify_only: t.verify_only,
|
||||
verify_required: t.verify_required as SoloTask['verify_required'],
|
||||
story_id: t.story.id,
|
||||
story_code: t.story.code,
|
||||
story_title: t.story.title,
|
||||
task_code: t.code,
|
||||
}))
|
||||
|
||||
const unassignedStories: UnassignedStory[] = rawUnassigned.map(s => ({
|
||||
id: s.id,
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ export default async function SprintBoardPage({ params, searchParams }: Props) {
|
|||
for (const story of sprintStories) {
|
||||
tasksByStory[story.id] = story.tasks.map(t => ({
|
||||
id: t.id,
|
||||
code: t.code,
|
||||
title: t.title,
|
||||
description: t.description,
|
||||
priority: t.priority,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export async function EditTaskLoader({
|
|||
},
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
title: true,
|
||||
description: true,
|
||||
implementation_plan: true,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import {
|
|||
} from '@/components/ui/alert-dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { CodeBadge } from '@/components/shared/code-badge'
|
||||
import { MAX_CODE_LENGTH } from '@/lib/code'
|
||||
import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
||||
import {
|
||||
useDirtyCloseGuard,
|
||||
|
|
@ -45,6 +47,7 @@ import { cn } from '@/lib/utils'
|
|||
|
||||
export interface TaskDialogTask {
|
||||
id: string
|
||||
code: string | null
|
||||
title: string
|
||||
description: string | null
|
||||
implementation_plan: string | null
|
||||
|
|
@ -88,6 +91,7 @@ export function TaskDialog({ task, storyId, productId, closePath, isDemo = false
|
|||
resolver: zodResolver(taskSchema),
|
||||
mode: 'onTouched',
|
||||
defaultValues: {
|
||||
code: task?.code ?? '',
|
||||
title: task?.title ?? '',
|
||||
description: task?.description ?? '',
|
||||
implementation_plan: task?.implementation_plan ?? '',
|
||||
|
|
@ -173,9 +177,12 @@ export function TaskDialog({ task, storyId, productId, closePath, isDemo = false
|
|||
>
|
||||
{/* Sticky header */}
|
||||
<div className={entityDialogHeaderClasses}>
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
{isEdit ? 'Taak bewerken' : 'Nieuwe taak'}
|
||||
</DialogTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
{isEdit ? 'Taak bewerken' : 'Nieuwe taak'}
|
||||
</DialogTitle>
|
||||
{isEdit && task?.code && <CodeBadge code={task.code} />}
|
||||
</div>
|
||||
{isEdit && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Aangemaakt:{' '}
|
||||
|
|
@ -190,6 +197,27 @@ export function TaskDialog({ task, storyId, productId, closePath, isDemo = false
|
|||
|
||||
{/* Scrollable form body */}
|
||||
<div className={entityDialogBodyClasses}>
|
||||
{/* Code */}
|
||||
<div>
|
||||
<label htmlFor="task-code" className="text-sm font-medium mb-2 block">
|
||||
Code
|
||||
</label>
|
||||
<Input
|
||||
id="task-code"
|
||||
{...form.register('code')}
|
||||
aria-invalid={!!form.formState.errors.code}
|
||||
placeholder="auto (T-1, T-2, ...)"
|
||||
className="font-mono"
|
||||
maxLength={MAX_CODE_LENGTH}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') e.preventDefault() }}
|
||||
/>
|
||||
{form.formState.errors.code && (
|
||||
<p className="text-xs text-destructive mt-1">
|
||||
{form.formState.errors.code.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">
|
||||
|
|
|
|||
|
|
@ -229,7 +229,6 @@ export function SprintBoardClient({
|
|||
<TaskList
|
||||
key="tasks"
|
||||
storyId={selectedStoryId}
|
||||
storyCode={stories.find(s => s.id === selectedStoryId)?.code ?? null}
|
||||
sprintId={sprintId}
|
||||
productId={productId}
|
||||
tasks={selectedTasks}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import { Badge } from '@/components/ui/badge'
|
|||
import { CodeBadge } from '@/components/shared/code-badge'
|
||||
import { PanelNavBar } from '@/components/shared/panel-nav-bar'
|
||||
import { PRIORITY_BORDER } from '@/components/backlog/backlog-card'
|
||||
import { deriveTaskCode } from '@/lib/code'
|
||||
import { useSprintStore } from '@/stores/sprint-store'
|
||||
import { updateTaskStatusAction, reorderTasksAction } from '@/actions/tasks'
|
||||
import { DemoTooltip } from '@/components/shared/demo-tooltip'
|
||||
|
|
@ -38,6 +37,7 @@ const STATUS_LABELS: Record<string, string> = { TO_DO: 'To Do', IN_PROGRESS: 'Be
|
|||
|
||||
export interface Task {
|
||||
id: string
|
||||
code: string
|
||||
title: string
|
||||
description: string | null
|
||||
priority: number
|
||||
|
|
@ -48,7 +48,6 @@ export interface Task {
|
|||
|
||||
interface TaskListProps {
|
||||
storyId: string
|
||||
storyCode: string | null
|
||||
sprintId: string
|
||||
productId: string
|
||||
tasks: Task[]
|
||||
|
|
@ -126,7 +125,7 @@ function SortableTaskRow({
|
|||
)
|
||||
}
|
||||
|
||||
export function TaskList({ storyId, storyCode, sprintId: _sprintId, productId: _productId, tasks, isDemo }: TaskListProps) {
|
||||
export function TaskList({ storyId, sprintId: _sprintId, productId: _productId, tasks, isDemo }: TaskListProps) {
|
||||
const { taskOrder, initTasks, reorderTasks, rollbackTasks } = useSprintStore()
|
||||
const [activeDragId, setActiveDragId] = useState<string | null>(null)
|
||||
const [, startTransition] = useTransition()
|
||||
|
|
@ -222,11 +221,11 @@ export function TaskList({ storyId, storyCode, sprintId: _sprintId, productId: _
|
|||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext items={orderedTasks.map(t => t.id)} strategy={verticalListSortingStrategy}>
|
||||
{orderedTasks.map((task, idx) => (
|
||||
{orderedTasks.map((task) => (
|
||||
<SortableTaskRow
|
||||
key={task.id}
|
||||
task={task}
|
||||
code={deriveTaskCode(storyCode, idx + 1)}
|
||||
code={task.code}
|
||||
isDemo={isDemo}
|
||||
onStatusToggle={() => handleStatusToggle(task)}
|
||||
onEdit={() => openEditDialog(task.id)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue