Scrum4Me/components/solo/solo-task-card.tsx
Janpeter Visser d292e445d9
Sprint: Verbeteren debug mode (#179)
* feat(PBI-49): add debugProps helper + Vitest test

Adds lib/debug.ts with debugProps(id, component, file) that returns
data-debug-id and data-debug-label attrs in dev mode, empty object in
production. Adds __tests__/lib/debug.test.ts covering both modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(PBI-49): add debug-id pattern doc + CLAUDE.md reference

Adds docs/patterns/debug-id.md documenting the named-component boundary
rule (6 punten), helper-voorbeeld, skip-criteria en motivatie voor
handmatige pad-argumenten. Voegt verwijzing toe aan CLAUDE.md
patterns-tabel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(PBI-49): migrate 17 shared/ components to debugProps helper

Replace hardcoded data-debug-id + data-debug-label attribute pairs with
{...debugProps(id, component, file)} spread in all 17 components/shared/
files. Existing debug-ids preserved unchanged.

* feat(PBI-49): add debugProps to backlog/, sprint/, solo/ components

* feat(PBI-49): add debugProps to jobs/ + ideas/ components

* feat(PBI-49): add debugProps to products/ + settings/ + notifications/ components

* feat(PBI-49): add debugProps to admin/ + dashboard/ + dialogs/ + mobile/ + split-pane/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(PBI-49): use attr(data-debug-id) for debug tooltip in globals.css

* refactor(PBI-49): remove data-debug-label from debugProps helper + test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(PBI-49): strip unused component/file args from debugProps in shared/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to StatusBar, NavBar, PanelNavBar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/sprint/*

- new-sprint-dialog: __submit on submit button
- sprint-backlog: __list on SprintBacklogLeft + SprintBacklogRight scroll areas
- sprint-board-client: root wrapper div (display:contents) + __drag-overlay
- sprint-header: __title on goal button, __dates on dates button, __actions on action cluster
- sprint-run-controls: root on controls div, __start/__cancel on action buttons; __blockers-dialog on dialog content
- start-sprint-button: root on trigger button, __dialog on dialog content, __submit on submit button
- sync-active-sprint-cookie: no debug-id (returns null, side-effect only), comment added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/backlog/*

* feat(PBI-49): add BEM sub-element data-debug-id to components/ideas/*

* feat(PBI-49): add BEM sub-element data-debug-id to components/dashboard/* + components/markdown.tsx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to new-product-button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/solo/*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-elements to nav-status-indicators

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/jobs/*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to components/products/*

* feat(PBI-49): add BEM sub-element data-debug-id to components/notifications/*

- answer-modal: __content (scroll area), __submit (footer)
- notifications-bridge: skip comment (bridge, non-rendering wrapper)
- notifications-realtime-mount: skip comment (returns null)
- notifications-sheet: __header, __items (questions list)
- push-toggle: __switch (button), __label (button text) on subscribed/unsubscribed states

* feat(PBI-49): add BEM sub-element data-debug-id to components/settings/*

- leave-product-button: root only (single-button component)
- min-quota-editor: __input (number input), __save (save button)
- profile-editor: __username (bio/short-description input), __save (submit)
- role-manager: __roles (checkbox list), __add (save button)
- token-manager: __tokens (active tokens list), __generate (create button)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(PBI-49): add BEM sub-element data-debug-id to admin, auth, dialogs, entity-dialog, mobile, split-pane

* docs(PBI-49): add debug-labels BEM pattern doc + CLAUDE.md entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:46:29 +02:00

173 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import type React from 'react'
import { useDraggable } from '@dnd-kit/core'
import { CSS } from '@dnd-kit/utilities'
import { Loader2 } from 'lucide-react'
import { cn } from '@/lib/utils'
import { CodeBadge } from '@/components/shared/code-badge'
import { JOB_STATUS_LABELS, JOB_STATUS_COLORS, JOB_STATUS_ACTIVE } from '@/components/shared/job-status'
import { useSoloStore } from '@/stores/solo-store'
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@/components/ui/tooltip'
import type { SoloTask } from './solo-board'
import { debugProps } from '@/lib/debug'
const PRIORITY_BORDER: Record<number, string> = {
1: 'border-l-4 border-l-priority-critical',
2: 'border-l-4 border-l-priority-high',
3: 'border-l-4 border-l-priority-medium',
4: 'border-l-4 border-l-priority-low',
}
interface SoloTaskCardProps {
task: SoloTask
isDemo: boolean
onClick: () => void
}
export function SoloTaskCard({ task, isDemo, onClick }: SoloTaskCardProps) {
const job = useSoloStore(s => s.claudeJobsByTaskId[task.id])
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
id: task.id,
disabled: isDemo,
})
const style: React.CSSProperties | undefined = transform
? { transform: CSS.Translate.toString(transform) }
: { viewTransitionName: `solo-task-${task.id}` }
return (
<div
ref={setNodeRef}
style={style}
onClick={onClick}
className={cn(
'bg-surface-container rounded border border-border px-3 py-2 select-none transition-colors',
PRIORITY_BORDER[task.priority],
isDragging ? 'opacity-40 z-50 shadow-lg' : 'hover:bg-surface-container-high',
isDemo ? 'cursor-pointer' : 'cursor-grab active:cursor-grabbing',
)}
{...(!isDemo ? { ...attributes, ...listeners } : {})}
{...debugProps('solo-task-card', 'SoloTaskCard', 'components/solo/solo-task-card.tsx')}
>
{/* Regel 1: taaknaam + task_code */}
<div className="flex items-start justify-between gap-2" data-debug-id="solo-task-card__title">
<p className="text-sm text-foreground leading-snug flex-1">{task.title}</p>
{task.task_code && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<span className="shrink-0 mt-0.5" />}>
<CodeBadge code={task.task_code} />
</TooltipTrigger>
<TooltipContent side="left">
<p className="font-semibold">{task.title}</p>
{task.description && (
<p className="text-muted-foreground italic">{task.description.slice(0, 100)}</p>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
{/* Regels 23: beschrijving + pbi_code */}
<div className="flex items-start justify-between gap-2 mt-0.5">
{task.description ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={
<p className="text-xs text-muted-foreground line-clamp-2 flex-1" />
}>
{task.description}
</TooltipTrigger>
{task.description.length > 80 && (
<TooltipContent side="bottom">
{task.description}
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
) : (
<div className="flex-1" />
)}
{task.pbi_code && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<span className="shrink-0" />}>
<CodeBadge code={task.pbi_code} />
</TooltipTrigger>
<TooltipContent side="left">
<p className="font-semibold">{task.pbi_title}</p>
{task.pbi_description && (
<p className="text-muted-foreground italic">{task.pbi_description.slice(0, 100)}</p>
)}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
{/* Regel 4: story-info + job-badge */}
<div className="flex items-center justify-between gap-2 mt-0.5" data-debug-id="solo-task-card__status">
<p className="text-xs text-muted-foreground truncate flex-1">
{task.story_code && <span className="font-mono mr-1">{task.story_code}</span>}
{task.story_title}
</p>
{job && (
<span
className={cn(
'text-[10px] px-1.5 py-0 rounded border flex items-center gap-1 shrink-0 cursor-pointer',
JOB_STATUS_COLORS[job.status],
)}
onClick={(e) => { e.stopPropagation(); onClick() }}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
e.stopPropagation()
onClick()
}
}}
role="button"
tabIndex={0}
aria-label={`Agent-status: ${JOB_STATUS_LABELS[job.status]}`}
>
{JOB_STATUS_ACTIVE.has(job.status) && <Loader2 className="animate-spin" size={8} />}
{JOB_STATUS_LABELS[job.status]}
</span>
)}
</div>
</div>
)
}
export function SoloTaskCardOverlay({ task }: { task: SoloTask }) {
return (
<div
className={cn(
'bg-surface-container rounded border border-primary px-3 py-2 shadow-xl opacity-90',
PRIORITY_BORDER[task.priority],
)}
{...debugProps('solo-task-card-overlay', 'SoloTaskCardOverlay', 'components/solo/solo-task-card.tsx')}
>
{/* Regel 1 */}
<div className="flex items-start justify-between gap-2">
<p className="text-sm text-foreground leading-snug flex-1">{task.title}</p>
{task.task_code && <CodeBadge code={task.task_code} className="shrink-0 mt-0.5" />}
</div>
{/* Regels 23 */}
<div className="flex items-start justify-between gap-2 mt-0.5">
{task.description ? (
<p className="text-xs text-muted-foreground line-clamp-2 flex-1">{task.description}</p>
) : (
<div className="flex-1" />
)}
{task.pbi_code && <CodeBadge code={task.pbi_code} className="shrink-0" />}
</div>
{/* Regel 4 */}
<p className="text-xs text-muted-foreground mt-0.5 truncate">
{task.story_code && <span className="font-mono mr-1">{task.story_code}</span>}
{task.story_title}
</p>
</div>
)
}