feat(ST-507): show code badges on cards, lists and dialogs across the app
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
66063f035a
commit
b71eb53fa8
15 changed files with 122 additions and 38 deletions
|
|
@ -15,7 +15,9 @@ import { toast } from 'sonner'
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { CodeBadge } from '@/components/shared/code-badge'
|
||||
import { PanelNavBar } from '@/components/shared/panel-nav-bar'
|
||||
import { deriveTaskCode } from '@/lib/code'
|
||||
import { useSprintStore } from '@/stores/sprint-store'
|
||||
import {
|
||||
createTaskAction, updateTaskStatusAction, updateTaskAction,
|
||||
|
|
@ -49,6 +51,7 @@ export interface Task {
|
|||
|
||||
interface TaskListProps {
|
||||
storyId: string
|
||||
storyCode: string | null
|
||||
sprintId: string
|
||||
productId: string
|
||||
tasks: Task[]
|
||||
|
|
@ -56,8 +59,8 @@ interface TaskListProps {
|
|||
}
|
||||
|
||||
function SortableTaskRow({
|
||||
task, isDemo, onStatusToggle, onDelete,
|
||||
}: { task: Task; isDemo: boolean; onStatusToggle: () => void; onDelete: () => void }) {
|
||||
task, code, isDemo, onStatusToggle, onDelete,
|
||||
}: { task: Task; code: string | null; isDemo: boolean; onStatusToggle: () => void; onDelete: () => void }) {
|
||||
const [editing, setEditing] = useState(false)
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: task.id })
|
||||
const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.4 : 1 }
|
||||
|
|
@ -88,23 +91,26 @@ function SortableTaskRow({
|
|||
}
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} className="group flex items-center gap-3 px-4 py-2.5 border-b border-border hover:bg-surface-container/50 transition-colors">
|
||||
<div ref={setNodeRef} style={style} className="group flex items-start gap-3 px-4 py-2.5 border-b border-border hover:bg-surface-container/50 transition-colors">
|
||||
{!isDemo && (
|
||||
<span {...attributes} {...listeners} className="text-muted-foreground cursor-grab active:cursor-grabbing shrink-0 text-sm select-none">⠿</span>
|
||||
<span {...attributes} {...listeners} className="text-muted-foreground cursor-grab active:cursor-grabbing shrink-0 text-sm select-none mt-0.5">⠿</span>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={cn('text-sm truncate', task.status === 'DONE' && 'line-through text-muted-foreground')}>
|
||||
{task.title}
|
||||
</p>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className={cn('text-sm truncate flex-1', task.status === 'DONE' && 'line-through text-muted-foreground')}>
|
||||
{task.title}
|
||||
</p>
|
||||
{code && <CodeBadge code={code} className="shrink-0 mt-0.5" />}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{PRIORITY_LABELS[task.priority]}</span>
|
||||
</div>
|
||||
<button onClick={onStatusToggle} disabled={isDemo} aria-label={`Status: ${STATUS_LABELS[task.status]}`}>
|
||||
<button onClick={onStatusToggle} disabled={isDemo} aria-label={`Status: ${STATUS_LABELS[task.status]}`} className="mt-0.5">
|
||||
<Badge className={cn('text-xs border cursor-pointer hover:opacity-80 transition-opacity', STATUS_COLORS[task.status])}>
|
||||
{STATUS_LABELS[task.status]}
|
||||
</Badge>
|
||||
</button>
|
||||
{!isDemo && (
|
||||
<div className="opacity-0 group-hover:opacity-100 flex gap-1">
|
||||
<div className="opacity-0 group-hover:opacity-100 flex gap-1 mt-0.5">
|
||||
<button onClick={() => setEditing(true)} className="text-xs text-muted-foreground hover:text-foreground">Bewerk</button>
|
||||
<button onClick={onDelete} aria-label="Verwijder taak" className="text-xs text-muted-foreground hover:text-error">×</button>
|
||||
</div>
|
||||
|
|
@ -150,7 +156,7 @@ function CreateSubmitButton() {
|
|||
return <Button type="submit" size="sm" className="h-7" disabled={pending}>{pending ? '…' : 'Toevoegen'}</Button>
|
||||
}
|
||||
|
||||
export function TaskList({ storyId, sprintId, productId: _productId, tasks, isDemo }: TaskListProps) {
|
||||
export function TaskList({ storyId, storyCode, sprintId, productId: _productId, tasks, isDemo }: TaskListProps) {
|
||||
const { taskOrder, initTasks, reorderTasks, rollbackTasks } = useSprintStore()
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [activeDragId, setActiveDragId] = useState<string | null>(null)
|
||||
|
|
@ -232,10 +238,11 @@ export function TaskList({ storyId, sprintId, productId: _productId, tasks, isDe
|
|||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext items={orderedTasks.map(t => t.id)} strategy={verticalListSortingStrategy}>
|
||||
{orderedTasks.map(task => (
|
||||
{orderedTasks.map((task, idx) => (
|
||||
<SortableTaskRow
|
||||
key={task.id}
|
||||
task={task}
|
||||
code={deriveTaskCode(storyCode, idx + 1)}
|
||||
isDemo={isDemo}
|
||||
onStatusToggle={() => handleStatusToggle(task)}
|
||||
onDelete={() => handleDelete(task.id)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue