fix(solo): TaskDetailDialog body scrollt + sticky header/footer
Story 6 zette entityDialogContentClasses op de buitenkant (flex flex-col p-0 gap-0 max-h-[85vh]) maar de binnenkant van TaskDetailContent gebruikte nog losse divs zonder shrink-0/flex-1 overflow-y-auto. Resultaat bij lange implementatieplannen: dialog groeide tot voorbij de viewport, header zat niet vast en footer-margin (-mx-4 -mb-4) brak omdat parent nu p-0 heeft. Fix: header in shrink-0 div met px-6 pt-5 pb-4 + border-b; body in entityDialogBodyClasses (flex-1 overflow-y-auto px-6 py-6 space-y-6); footer in entityDialogFooterClasses + flex-wrap voor de variabele job-status-knoppen. Plan-textarea krijgt max-h-[40vh] zodat een lang plan niet meteen heel het body-gebied opvult. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e1f1f29db7
commit
61b3db195c
1 changed files with 75 additions and 69 deletions
|
|
@ -4,8 +4,12 @@ import { useRef, useState, useTransition } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { Markdown } from '@/components/markdown'
|
import { Markdown } from '@/components/markdown'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { entityDialogContentClasses } from '@/components/shared/entity-dialog-layout'
|
import {
|
||||||
|
entityDialogBodyClasses,
|
||||||
|
entityDialogContentClasses,
|
||||||
|
entityDialogFooterClasses,
|
||||||
|
} from '@/components/shared/entity-dialog-layout'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Textarea } from '@/components/ui/textarea'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
|
@ -182,8 +186,8 @@ function TaskDetailContent({ task, productId, isDemo, repoUrl, onClose }: TaskDe
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogHeader>
|
<div className="flex flex-col gap-1 px-6 pt-5 pb-4 border-b border-outline-variant shrink-0">
|
||||||
<div className="flex items-start gap-3 pr-8">
|
<div className="flex items-start gap-3">
|
||||||
<DialogTitle className="text-sm font-medium leading-snug flex-1">
|
<DialogTitle className="text-sm font-medium leading-snug flex-1">
|
||||||
{task.title}
|
{task.title}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
@ -200,78 +204,80 @@ function TaskDetailContent({ task, productId, isDemo, repoUrl, onClose }: TaskDe
|
||||||
{task.story_code && <span className="font-mono mr-1">{task.story_code}</span>}
|
{task.story_code && <span className="font-mono mr-1">{task.story_code}</span>}
|
||||||
{task.story_title}
|
{task.story_title}
|
||||||
</p>
|
</p>
|
||||||
</DialogHeader>
|
</div>
|
||||||
|
|
||||||
|
<div className={entityDialogBodyClasses}>
|
||||||
|
{task.description && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-medium text-muted-foreground mb-1.5">Beschrijving</p>
|
||||||
|
<Markdown className="text-foreground">{task.description}</Markdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{task.description && (
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-muted-foreground mb-1.5">Beschrijving</p>
|
<p className="text-xs font-medium text-muted-foreground mb-1.5">Implementatieplan</p>
|
||||||
<Markdown className="text-foreground">{task.description}</Markdown>
|
<DemoTooltip show={isDemo}>
|
||||||
|
<Textarea
|
||||||
|
value={localPlan}
|
||||||
|
onChange={(e) => setLocalPlan(e.target.value)}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
placeholder="Voeg een implementatieplan toe…"
|
||||||
|
className="resize-none text-sm min-h-[120px] max-h-[40vh]"
|
||||||
|
readOnly={isDemo}
|
||||||
|
/>
|
||||||
|
</DemoTooltip>
|
||||||
|
<div className="flex justify-end mt-1 h-4">
|
||||||
|
{saveState === 'saving' && (
|
||||||
|
<span className="text-xs text-muted-foreground">Bezig met opslaan…</span>
|
||||||
|
)}
|
||||||
|
{saveState === 'saved' && (
|
||||||
|
<span className="text-xs text-status-done">Opgeslagen</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-xs font-medium text-muted-foreground mb-1.5">Implementatieplan</p>
|
<DemoTooltip show={isDemo}>
|
||||||
<DemoTooltip show={isDemo}>
|
<button
|
||||||
<Textarea
|
type="button"
|
||||||
value={localPlan}
|
role="checkbox"
|
||||||
onChange={(e) => setLocalPlan(e.target.value)}
|
aria-checked={localVerifyOnly}
|
||||||
onBlur={handleBlur}
|
onClick={handleVerifyOnlyToggle}
|
||||||
placeholder="Voeg een implementatieplan toe…"
|
disabled={isDemo || verifyOnlyPending}
|
||||||
className="resize-none text-sm min-h-[120px]"
|
className={cn(
|
||||||
readOnly={isDemo}
|
'h-4 w-4 rounded border border-border flex items-center justify-center shrink-0',
|
||||||
/>
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
</DemoTooltip>
|
localVerifyOnly && 'bg-primary border-primary',
|
||||||
<div className="flex justify-end mt-1 h-4">
|
)}
|
||||||
{saveState === 'saving' && (
|
>
|
||||||
<span className="text-xs text-muted-foreground">Bezig met opslaan…</span>
|
{localVerifyOnly && (
|
||||||
)}
|
<svg className="h-3 w-3 text-primary-foreground" viewBox="0 0 12 12" fill="none">
|
||||||
{saveState === 'saved' && (
|
<path d="M2 6l3 3 5-5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
<span className="text-xs text-status-done">Opgeslagen</span>
|
</svg>
|
||||||
)}
|
)}
|
||||||
|
</button>
|
||||||
|
</DemoTooltip>
|
||||||
|
<span className="text-xs text-muted-foreground">Alleen verifiëren (niet implementeren)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs text-muted-foreground shrink-0">Verify-gate:</span>
|
||||||
|
<DemoTooltip show={isDemo}>
|
||||||
|
<select
|
||||||
|
value={localVerifyRequired}
|
||||||
|
onChange={handleVerifyRequiredChange}
|
||||||
|
disabled={isDemo || verifyRequiredPending}
|
||||||
|
className="text-xs rounded-md border border-border bg-surface-container px-2 py-1 text-foreground focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{(['ALIGNED', 'ALIGNED_OR_PARTIAL', 'ANY'] as const).map(v => (
|
||||||
|
<option key={v} value={v}>{VERIFY_REQUIRED_LABELS[v]}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</DemoTooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className={cn(entityDialogFooterClasses, 'flex flex-wrap items-center gap-2')}>
|
||||||
<DemoTooltip show={isDemo}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="checkbox"
|
|
||||||
aria-checked={localVerifyOnly}
|
|
||||||
onClick={handleVerifyOnlyToggle}
|
|
||||||
disabled={isDemo || verifyOnlyPending}
|
|
||||||
className={cn(
|
|
||||||
'h-4 w-4 rounded border border-border flex items-center justify-center shrink-0',
|
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
||||||
localVerifyOnly && 'bg-primary border-primary',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{localVerifyOnly && (
|
|
||||||
<svg className="h-3 w-3 text-primary-foreground" viewBox="0 0 12 12" fill="none">
|
|
||||||
<path d="M2 6l3 3 5-5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</DemoTooltip>
|
|
||||||
<span className="text-xs text-muted-foreground">Alleen verifiëren (niet implementeren)</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-muted-foreground shrink-0">Verify-gate:</span>
|
|
||||||
<DemoTooltip show={isDemo}>
|
|
||||||
<select
|
|
||||||
value={localVerifyRequired}
|
|
||||||
onChange={handleVerifyRequiredChange}
|
|
||||||
disabled={isDemo || verifyRequiredPending}
|
|
||||||
className="text-xs rounded-md border border-border bg-surface-container px-2 py-1 text-foreground focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{(['ALIGNED', 'ALIGNED_OR_PARTIAL', 'ANY'] as const).map(v => (
|
|
||||||
<option key={v} value={v}>{VERIFY_REQUIRED_LABELS[v]}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</DemoTooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="-mx-4 -mb-4 flex flex-wrap items-center gap-2 border-t bg-muted/50 px-4 py-3 rounded-b-xl">
|
|
||||||
<Link
|
<Link
|
||||||
href={`/products/${productId}/sprint/planning`}
|
href={`/products/${productId}/sprint/planning`}
|
||||||
className="text-xs text-primary hover:underline mr-auto"
|
className="text-xs text-primary hover:underline mr-auto"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue