feat(backlog): add EmptyPanel shared component, replace inline empty states

EmptyPanel takes title?, message, and optional action with DemoTooltip.
Replaces duplicate inline empty-state markup in pbi-list and story-panel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-30 17:20:35 +02:00
parent 75bbb1ad73
commit 57e2d49949
3 changed files with 46 additions and 21 deletions

View file

@ -0,0 +1,35 @@
'use client'
import { Button } from '@/components/ui/button'
import { DemoTooltip } from '@/components/shared/demo-tooltip'
interface EmptyPanelProps {
title?: string
message: string
action?: {
label: string
onClick: () => void
disabled?: boolean
}
}
export function EmptyPanel({ title, message, action }: EmptyPanelProps) {
return (
<div className="p-8 text-center text-muted-foreground space-y-3">
{title && <p className="text-sm font-medium text-foreground">{title}</p>}
<p className="text-sm">{message}</p>
{action && (
<DemoTooltip show={action.disabled ?? false}>
<Button
size="sm"
variant="outline"
disabled={action.disabled}
onClick={action.disabled ? undefined : action.onClick}
>
{action.label}
</Button>
</DemoTooltip>
)}
</div>
)
}

View file

@ -32,6 +32,7 @@ import { reorderPbisAction, updatePbiPriorityAction } from '@/actions/stories'
import { cn } from '@/lib/utils'
import { PbiDialog, type PbiDialogState } from './pbi-dialog'
import { BacklogCard } from './backlog-card'
import { EmptyPanel } from './empty-panel'
import { DemoTooltip } from '@/components/shared/demo-tooltip'
import { PRIORITY_COLORS } from '@/components/shared/priority-select'
import { PBI_STATUS_LABELS, PBI_STATUS_COLORS } from '@/components/shared/pbi-status-select'
@ -417,14 +418,10 @@ export function PbiList({ productId, pbis, isDemo }: PbiListProps) {
<div className="flex-1 overflow-y-auto">
{pbis.length === 0 ? (
<div className="p-8 text-center text-muted-foreground text-sm space-y-3">
<p>Nog geen PBI&apos;s aangemaakt.</p>
<DemoTooltip show={isDemo}>
<Button size="sm" variant="outline" disabled={isDemo} onClick={() => !isDemo && setDialogState({ mode: 'create', productId, defaultPriority: 2 })}>
Maak je eerste PBI aan
</Button>
</DemoTooltip>
</div>
<EmptyPanel
message="Nog geen PBI's aangemaakt."
action={{ label: 'Maak je eerste PBI aan', onClick: () => setDialogState({ mode: 'create', productId, defaultPriority: 2 }), disabled: isDemo }}
/>
) : (
<DndContext
id="pbi-list"

View file

@ -30,6 +30,7 @@ import { usePlannerStore } from '@/stores/planner-store'
import { reorderStoriesAction } from '@/actions/stories'
import { StoryDialog, type StoryDialogState } from './story-dialog'
import { BacklogCard } from './backlog-card'
import { EmptyPanel } from './empty-panel'
import { DemoTooltip } from '@/components/shared/demo-tooltip'
import { cn } from '@/lib/utils'
@ -242,20 +243,12 @@ export function StoryPanel({ productId, storiesByPbi, isDemo }: StoryPanelProps)
<div className="flex-1 overflow-y-auto p-4">
{selectedPbiId === null ? (
<p className="text-sm text-muted-foreground text-center mt-8">
Selecteer een PBI om de stories te bekijken.
</p>
<EmptyPanel message="Selecteer een PBI om de stories te bekijken." />
) : rawStories.length === 0 ? (
<div className="text-center mt-8 space-y-3">
<p className="text-sm text-muted-foreground">Nog geen stories voor dit PBI.</p>
{selectedPbiId && (
<DemoTooltip show={isDemo}>
<Button size="sm" variant="outline" disabled={isDemo} onClick={() => !isDemo && setStoryDialogState({ mode: 'create', pbiId: selectedPbiId, productId, defaultPriority: 2 })}>
Maak je eerste story aan
</Button>
</DemoTooltip>
)}
</div>
<EmptyPanel
message="Nog geen stories voor dit PBI."
action={{ label: 'Maak je eerste story aan', onClick: () => setStoryDialogState({ mode: 'create', pbiId: selectedPbiId, productId, defaultPriority: 2 }), disabled: isDemo }}
/>
) : (
<DndContext
id="story-panel"