Files
triqura-ecd/components/cortex/blocks/noshow-document-block.tsx
colinislit 924988dd15 feat(cortex): no-show casus — intent flow, brief-rescript en document artifact
Cortex handelt een no-show af vanuit één chatcommando: afspraak
annuleren (declarabiliteits-nudge), en de openstaande concept-
huisartsbrief wordt via LLM herschreven en ter review aangeboden
in een document artifact (human-in-the-loop, PATCH dispatch zet
status op verzendklaar).

- API-routes: context, rescript, cancel, dispatch
- NoShowDocumentBlock: review/edit UI met origineel-vergelijk
- Mock-data voor concept huisartsbrief
- PRD, FO, bouwplan en epics in docs/intent/noshow-case/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 23:14:55 +02:00

141 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState } from 'react';
import { FileText, ChevronDown, ChevronUp } from 'lucide-react';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
import { useCortexStore } from '@/stores/cortex-store';
import { cn } from '@/lib/utils';
interface NoShowDocumentPrefill {
documentId: string;
content: string;
title: string;
originalContent?: string;
rescriptWarning?: string;
}
interface NoShowDocumentBlockProps {
prefill: NoShowDocumentPrefill;
}
export function NoShowDocumentBlock({ prefill }: NoShowDocumentBlockProps) {
const [content, setContent] = useState<string>(prefill.content ?? '');
const [showOriginal, setShowOriginal] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isDone, setIsDone] = useState(false);
const addChatMessage = useCortexStore((s) => s.addChatMessage);
const setNoShowStep = useCortexStore((s) => s.setNoShowStep);
const handleDispatch = async () => {
setIsSubmitting(true);
try {
const res = await fetch('/api/cortex/noshow/dispatch', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
documentId: prefill.documentId,
finalContent: content,
}),
});
if (!res.ok) throw new Error('Dispatch mislukt');
setIsDone(true);
setNoShowStep('done');
addChatMessage({
type: 'assistant',
content: 'Brief is verzendklaar gemaakt. De no-show afhandeling is compleet.',
});
} catch {
addChatMessage({
type: 'error',
content: 'Opslaan mislukt. Probeer het opnieuw.',
});
} finally {
setIsSubmitting(false);
}
};
return (
<div className="w-full max-w-2xl">
{/* Header */}
<div className="flex items-center gap-2 mb-4">
<FileText className="w-5 h-5 text-slate-600" />
<h2 className="text-lg font-semibold text-slate-800">
{prefill.title || 'Huisartsbrief'}
</h2>
<span className="ml-auto text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded font-medium">
Aangepast door Cortex
</span>
</div>
{/* LLM waarschuwing bij fallback naar origineel */}
{prefill.rescriptWarning && (
<div className="mb-3 p-3 bg-amber-50 border border-amber-200 rounded text-sm text-amber-800">
{prefill.rescriptWarning}
</div>
)}
{/* Bewerkbare tekstinhoud */}
<Textarea
value={content}
onChange={(e) => setContent(e.target.value)}
disabled={isDone}
className={cn(
'min-h-[320px] font-mono text-sm resize-y',
isDone && 'opacity-60 cursor-not-allowed'
)}
placeholder="Brief inhoud..."
/>
{/* Origineel bekijken — collapsible */}
{prefill.originalContent && (
<div className="mt-2">
<button
type="button"
onClick={() => setShowOriginal(!showOriginal)}
className="flex items-center gap-1 text-xs text-slate-500 hover:text-slate-700 transition-colors"
>
{showOriginal ? (
<ChevronUp className="w-3 h-3" />
) : (
<ChevronDown className="w-3 h-3" />
)}
{showOriginal ? 'Origineel verbergen' : 'Originele brief bekijken'}
</button>
{showOriginal && (
<div className="mt-2 p-3 bg-slate-50 border border-slate-200 rounded text-sm text-slate-500 font-mono whitespace-pre-wrap">
{prefill.originalContent}
</div>
)}
</div>
)}
{/* Footer */}
<div className="mt-4 flex items-center justify-between border-t border-slate-200 pt-4">
<span className="text-xs text-slate-400">
{isDone ? '✓ Brief is verzendklaar' : 'Controleer en pas aan indien nodig'}
</span>
{isDone ? (
<span className="text-sm font-medium text-green-600">
Verzendklaar
</span>
) : (
<Button
onClick={handleDispatch}
disabled={isSubmitting || content.trim().length === 0}
className="bg-slate-800 hover:bg-slate-700 text-white"
>
{isSubmitting ? 'Opslaan...' : 'Akkoord & Verzendklaar →'}
</Button>
)}
</div>
</div>
);
}