feat(backlog): migrate StoryPanel to store-driven + selectStory on click

Removes storiesByPbi prop; reads from useBacklogStore. Card click now
dispatches selectStory(id) + shows isSelected highlight. Edit moved to
inline pencil button. page.tsx drops pbis/storiesByPbi props.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-30 17:27:27 +02:00
parent 33d1c2312a
commit c495d29313
2 changed files with 27 additions and 9 deletions

View file

@ -137,13 +137,11 @@ export default async function ProductBacklogPage({ params, searchParams }: Props
<PbiList
key="pbi"
productId={id}
pbis={pbis.map((p: (typeof pbis)[number]) => ({ id: p.id, code: p.code, title: p.title, priority: p.priority, description: p.description, created_at: p.created_at, status: pbiStatusToApi(p.status) }))}
isDemo={isDemo}
/>,
<StoryPanel
key="story"
productId={id}
storiesByPbi={storiesByPbi}
isDemo={isDemo}
/>,
<TaskPanel

View file

@ -27,6 +27,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { PanelNavBar } from '@/components/shared/panel-nav-bar'
import { useSelectionStore } from '@/stores/selection-store'
import { usePlannerStore } from '@/stores/planner-store'
import { useBacklogStore } from '@/stores/backlog-store'
import { reorderStoriesAction } from '@/actions/stories'
import { StoryDialog, type StoryDialogState } from './story-dialog'
import { BacklogCard } from './backlog-card'
@ -60,17 +61,20 @@ export interface Story {
interface StoryPanelProps {
productId: string
storiesByPbi: Record<string, Story[]>
isDemo: boolean
}
// --- Sortable story block ---
function SortableStoryBlock({
story,
onClick,
isSelected,
onSelect,
onEdit,
}: {
story: Story
onClick: () => void
isSelected: boolean
onSelect: () => void
onEdit: () => void
}) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: story.id,
@ -92,19 +96,33 @@ function SortableStoryBlock({
code={story.code}
priority={story.priority}
isDragging={isDragging}
onClick={onClick}
isSelected={isSelected}
onClick={onSelect}
badge={
<Badge className={cn('text-[10px] px-1.5 py-0 border', STATUS_COLORS[story.status])}>
{STATUS_LABELS[story.status] ?? story.status}
</Badge>
}
actions={
<button
onClick={(e) => { e.stopPropagation(); onEdit() }}
className="text-muted-foreground hover:text-foreground p-0.5 rounded transition-colors"
aria-label="Story bewerken"
>
<svg className="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
</svg>
</button>
}
/>
)
}
// --- Main component ---
export function StoryPanel({ productId, storiesByPbi, isDemo }: StoryPanelProps) {
const { selectedPbiId } = useSelectionStore()
export function StoryPanel({ productId, isDemo }: StoryPanelProps) {
const { selectedPbiId, selectedStoryId, selectStory } = useSelectionStore()
const storiesByPbi = useBacklogStore((s) => s.storiesByPbi)
const { storyOrder, initStories, reorderStories, rollbackStories } = usePlannerStore()
const [filterStatus, setFilterStatus] = useState<string | null>(null)
const [filterPriority, setFilterPriority] = useState<number | null>(null)
@ -263,7 +281,9 @@ export function StoryPanel({ productId, storiesByPbi, isDemo }: StoryPanelProps)
<SortableStoryBlock
key={story.id}
story={story}
onClick={() => setStoryDialogState({ mode: 'edit', story, productId })}
isSelected={selectedStoryId === story.id}
onSelect={() => selectStory(story.id)}
onEdit={() => setStoryDialogState({ mode: 'edit', story, productId })}
/>
))}
</div>