feat: Ideas UI verbeteringen — hernoeming, tab-states, timeline refresh
- Nav-label 'Ideeën' hernoemd naar 'Ideas'; breadcrumb idem - Grill/Plan tabs disabled (grijs, cursor-not-allowed) zolang er geen content is; groene stip zodra grill_md resp. plan_md beschikbaar is - SSE hook roept router.refresh() aan bij job done/failed zodat de Timeline automatisch de nieuwe GRILL_RESULT/PLAN_RESULT logs toont Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
474a8da053
commit
649c87b658
3 changed files with 27 additions and 15 deletions
|
|
@ -40,13 +40,6 @@ const API_TO_DB: Record<IdeaStatusApi, Parameters<typeof getIdeaStatusBadge>[0]>
|
|||
|
||||
type TabKey = 'idee' | 'grill' | 'plan' | 'timeline'
|
||||
|
||||
const TABS: { key: TabKey; label: string }[] = [
|
||||
{ key: 'idee', label: 'Idee' },
|
||||
{ key: 'grill', label: 'Grill' },
|
||||
{ key: 'plan', label: 'Plan' },
|
||||
{ key: 'timeline', label: 'Timeline' },
|
||||
]
|
||||
|
||||
interface IdeaLog {
|
||||
id: string
|
||||
type: string
|
||||
|
|
@ -106,7 +99,8 @@ export function IdeaDetailLayout({
|
|||
const searchParams = useSearchParams()
|
||||
const [pending, startTransition] = useTransition()
|
||||
|
||||
const tab = (TABS.some((t) => t.key === initialTab) ? initialTab : 'idee') as TabKey
|
||||
const TAB_KEYS: TabKey[] = ['idee', 'grill', 'plan', 'timeline']
|
||||
const tab = (TAB_KEYS.includes(initialTab as TabKey) ? initialTab : 'idee') as TabKey
|
||||
|
||||
function setTab(key: TabKey) {
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
|
|
@ -138,7 +132,7 @@ export function IdeaDetailLayout({
|
|||
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
Alle ideeën
|
||||
Alle ideas
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
|
|
@ -171,18 +165,29 @@ export function IdeaDetailLayout({
|
|||
|
||||
{/* Tab-switcher */}
|
||||
<nav className="border-b border-input flex gap-1">
|
||||
{TABS.map((t) => (
|
||||
{([
|
||||
{ key: 'idee' as TabKey, label: 'Idee', disabled: false, hasContent: true },
|
||||
{ key: 'grill' as TabKey, label: 'Grill', disabled: !grill_md, hasContent: !!grill_md },
|
||||
{ key: 'plan' as TabKey, label: 'Plan', disabled: !plan_md, hasContent: !!plan_md },
|
||||
{ key: 'timeline' as TabKey, label: 'Timeline', disabled: false, hasContent: true },
|
||||
] as const).map((t) => (
|
||||
<button
|
||||
key={t.key}
|
||||
type="button"
|
||||
onClick={() => setTab(t.key)}
|
||||
onClick={() => !t.disabled && setTab(t.key)}
|
||||
disabled={t.disabled}
|
||||
className={`px-4 py-2 text-sm border-b-2 transition-colors ${
|
||||
tab === t.key
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
t.disabled
|
||||
? 'border-transparent text-muted-foreground/40 cursor-not-allowed'
|
||||
: tab === t.key
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
{t.label}
|
||||
{t.hasContent && !t.disabled && t.key !== 'idee' && t.key !== 'timeline' && (
|
||||
<span className="ml-1 text-[10px] text-status-done">●</span>
|
||||
)}
|
||||
{t.key === 'timeline' && (logs.length > 0 || questions.length > 0) ? (
|
||||
<span className="ml-1.5 text-xs text-muted-foreground">
|
||||
({logs.length + questions.length})
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export function NavBar({
|
|||
)
|
||||
: disabledSpan('Solo')}
|
||||
{navLink('/insights', 'Insights', pathname.startsWith('/insights'))}
|
||||
{navLink('/ideas', 'Ideeën', pathname.startsWith('/ideas'))}
|
||||
{navLink('/ideas', 'Ideas', pathname.startsWith('/ideas'))}
|
||||
{navLink('/todos', "Todo's", pathname.startsWith('/todos'))}
|
||||
{roles.includes('ADMIN') && navLink('/admin', 'Admin', pathname.startsWith('/admin'))}
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -122,6 +122,13 @@ export function useNotificationsRealtime() {
|
|||
status: payload.status as 'queued',
|
||||
error: payload.error,
|
||||
})
|
||||
// Refresh zodra job klaar is — server heeft nu grill_md/plan_md
|
||||
// geschreven en de idea-status bijgewerkt. router.refresh() triggert
|
||||
// een server-component re-fetch zodat de Timeline de nieuwe
|
||||
// GRILL_RESULT/PLAN_RESULT logs en de bijgewerkte status oppikt.
|
||||
if (payload.status === 'done' || payload.status === 'failed') {
|
||||
router.refresh()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue