Compare commits
1 commit
main
...
feat/story
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82736fd051 |
2 changed files with 112 additions and 4 deletions
|
|
@ -44,7 +44,7 @@ const TABS: { key: TabKey; label: string }[] = [
|
|||
{ key: 'idee', label: 'Idee' },
|
||||
{ key: 'grill', label: 'Grill' },
|
||||
{ key: 'plan', label: 'Plan' },
|
||||
{ key: 'timeline', label: 'Timeline' },
|
||||
{ key: 'timeline', label: 'Chat & Timeline' },
|
||||
]
|
||||
|
||||
interface IdeaLog {
|
||||
|
|
@ -221,7 +221,15 @@ export function IdeaDetailLayout({
|
|||
ideaId={idea.id}
|
||||
/>
|
||||
)}
|
||||
{tab === 'timeline' && <IdeaTimeline logs={logs} questions={questions} />}
|
||||
{tab === 'timeline' && (
|
||||
<IdeaTimeline
|
||||
logs={logs}
|
||||
questions={questions}
|
||||
userQuestions={userQuestions}
|
||||
planMd={plan_md}
|
||||
ideaId={idea.id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
FileText,
|
||||
HelpCircle,
|
||||
Lightbulb,
|
||||
MessageCircle,
|
||||
RefreshCw,
|
||||
StickyNote,
|
||||
Wrench,
|
||||
|
|
@ -24,6 +25,7 @@ import { toast } from 'sonner'
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { answerQuestion } from '@/actions/questions'
|
||||
import { createUserQuestionAction } from '@/actions/user-questions'
|
||||
|
||||
import type { IdeaLogType } from '@prisma/client'
|
||||
|
||||
|
|
@ -45,9 +47,20 @@ export interface TimelineQuestion {
|
|||
expires_at: string
|
||||
}
|
||||
|
||||
export interface TimelineUserQuestion {
|
||||
id: string
|
||||
question: string
|
||||
answer: string | null
|
||||
status: 'pending' | 'answered'
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
logs: TimelineLog[]
|
||||
questions: TimelineQuestion[]
|
||||
userQuestions: TimelineUserQuestion[]
|
||||
planMd: string | null
|
||||
ideaId: string
|
||||
}
|
||||
|
||||
const LOG_ICON: Record<IdeaLogType, React.ReactNode> = {
|
||||
|
|
@ -75,7 +88,7 @@ const QUESTION_STATUS_LABEL: Record<TimelineQuestion['status'], string> = {
|
|||
expired: 'Verlopen',
|
||||
}
|
||||
|
||||
export function IdeaTimeline({ logs, questions }: Props) {
|
||||
export function IdeaTimeline({ logs, questions, userQuestions, planMd, ideaId }: Props) {
|
||||
const merged = [
|
||||
...logs.map((l) => ({
|
||||
kind: 'log' as const,
|
||||
|
|
@ -87,9 +100,14 @@ export function IdeaTimeline({ logs, questions }: Props) {
|
|||
created_at: q.created_at,
|
||||
data: q,
|
||||
})),
|
||||
...userQuestions.map((uq) => ({
|
||||
kind: 'user_question' as const,
|
||||
created_at: uq.created_at,
|
||||
data: uq,
|
||||
})),
|
||||
].sort((a, b) => (a.created_at < b.created_at ? 1 : -1))
|
||||
|
||||
if (merged.length === 0) {
|
||||
if (merged.length === 0 && !planMd) {
|
||||
return (
|
||||
<p className="text-sm text-muted-foreground py-8 text-center italic">
|
||||
Nog geen activiteit op dit idee.
|
||||
|
|
@ -98,6 +116,7 @@ export function IdeaTimeline({ logs, questions }: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ol className="border-l-2 border-input pl-4 space-y-3 ml-2">
|
||||
{merged.map((entry, i) => {
|
||||
// Expliciete locale + format om SSR/CSR hydration-mismatch te voorkomen
|
||||
|
|
@ -138,6 +157,37 @@ export function IdeaTimeline({ logs, questions }: Props) {
|
|||
)
|
||||
}
|
||||
|
||||
if (entry.kind === 'user_question') {
|
||||
const uq = entry.data
|
||||
return (
|
||||
<li key={`uq-${uq.id}-${i}`} className="relative">
|
||||
<span className="absolute -left-[26px] top-1 flex size-5 items-center justify-center rounded-full bg-surface-container text-primary">
|
||||
<MessageCircle className="size-4" />
|
||||
</span>
|
||||
<div className="rounded-md border border-input bg-surface-container p-3 space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span className="font-medium uppercase tracking-wide">Jouw vraag</span>
|
||||
<span>·</span>
|
||||
<time>{time}</time>
|
||||
</div>
|
||||
<p className="text-sm">{uq.question}</p>
|
||||
{uq.status === 'pending' ? (
|
||||
<p className="text-xs text-muted-foreground italic">
|
||||
Wacht op antwoord van Claude...
|
||||
</p>
|
||||
) : uq.answer ? (
|
||||
<div className="mt-2 rounded bg-muted/50 p-3 text-sm">
|
||||
<span className="text-xs font-medium uppercase tracking-wide text-primary mr-2">
|
||||
Claude
|
||||
</span>
|
||||
{uq.answer}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
const q = entry.data
|
||||
return (
|
||||
<li key={`q-${q.id}-${i}`} className="relative">
|
||||
|
|
@ -179,6 +229,12 @@ export function IdeaTimeline({ logs, questions }: Props) {
|
|||
)
|
||||
})}
|
||||
</ol>
|
||||
{planMd && (
|
||||
<div className="mt-4 border-t pt-4">
|
||||
<UserChatInput ideaId={ideaId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -269,3 +325,47 @@ function AnswerForm({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UserChatInput — stel een vraag aan Claude over het plan van dit idee.
|
||||
|
||||
function UserChatInput({ ideaId }: { ideaId: string }) {
|
||||
const router = useRouter()
|
||||
const [question, setQuestion] = useState('')
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
function submit() {
|
||||
const trimmed = question.trim()
|
||||
if (!trimmed) return
|
||||
startTransition(async () => {
|
||||
const res = await createUserQuestionAction(ideaId, trimmed)
|
||||
if ('success' in res && res.success) {
|
||||
setQuestion('')
|
||||
toast.success('Vraag verstuurd — Claude antwoordt zodra mogelijk.')
|
||||
router.refresh()
|
||||
} else if ('error' in res) {
|
||||
toast.error(res.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Stel een vraag aan Claude
|
||||
</p>
|
||||
<Textarea
|
||||
value={question}
|
||||
onChange={(e) => setQuestion(e.target.value)}
|
||||
rows={3}
|
||||
placeholder="Vraag iets over het plan…"
|
||||
disabled={isPending}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" disabled={isPending || !question.trim()} onClick={submit}>
|
||||
Vraag stellen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue