+ {/* Speech recorder (collapsible) */}
+ {showRecorder && (
+
+
+
+ )}
+
+ {/* AI Classification result (inline) */}
+ {classification && (
+
+
+
+ AI suggereert: {classification.type === 'behandeladvies' ? 'Behandeladvies' : 'Vrije notitie'}
+ ({Math.round(classification.confidence * 100)}% zeker)
+
+
+ )}
+
+ {error &&
{error}
}
+
+ {/* Footer */}
+
{isSaving
? 'Opslaan bezig…'
@@ -338,13 +356,10 @@ export function ReportComposer({
onClick={analyzeWithAI}
disabled={contentInvalid || isAnalyzing || isSaving}
>
+
{isAnalyzing ? 'Analyseren…' : 'Analyseer met AI'}
-
+
{isSaving ? 'Opslaan…' : 'Opslaan'}
diff --git a/components/rich-text-editor.tsx b/components/rich-text-editor.tsx
index eb49dab..2faaa55 100644
--- a/components/rich-text-editor.tsx
+++ b/components/rich-text-editor.tsx
@@ -1,7 +1,7 @@
'use client';
-import { useEffect } from 'react';
-import { EditorContent, useEditor } from '@tiptap/react';
+import { useEffect, type ReactNode } from 'react';
+import { EditorContent, useEditor, type Editor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
import { Bold, Italic, List, ListOrdered, Quote } from 'lucide-react';
@@ -11,9 +11,25 @@ interface RichTextEditorProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
+ /** Extra content voor in de toolbar (bijv. mic button) */
+ toolbarExtra?: ReactNode;
+ /** Callback met editor instance voor externe controle */
+ onEditorReady?: (editor: Editor) => void;
+ /** Highlight border tijdens streaming */
+ isStreaming?: boolean;
+ /** Min height voor editor */
+ minHeight?: string;
}
-export function RichTextEditor({ value, onChange, placeholder }: RichTextEditorProps) {
+export function RichTextEditor({
+ value,
+ onChange,
+ placeholder,
+ toolbarExtra,
+ onEditorReady,
+ isStreaming = false,
+ minHeight = '160px',
+}: RichTextEditorProps) {
const editor = useEditor({
extensions: [
StarterKit.configure({
@@ -26,12 +42,12 @@ export function RichTextEditor({ value, onChange, placeholder }: RichTextEditorP
content: value || '',
editorProps: {
attributes: {
- class:
- 'prose prose-sm max-w-none focus:outline-none min-h-[160px] px-3 py-2 text-slate-800 bg-white',
+ class: `prose prose-sm max-w-none focus:outline-none px-3 py-2 text-slate-800 bg-white`,
+ style: `min-height: ${minHeight}`,
},
},
- onUpdate({ editor }) {
- onChange?.(editor.getHTML());
+ onUpdate({ editor: e }) {
+ onChange?.(e.getHTML());
},
immediatelyRender: false,
});
@@ -44,6 +60,13 @@ export function RichTextEditor({ value, onChange, placeholder }: RichTextEditorP
}
}, [value, editor]);
+ // Notify parent when editor is ready
+ useEffect(() => {
+ if (editor && onEditorReady) {
+ onEditorReady(editor);
+ }
+ }, [editor, onEditorReady]);
+
if (!editor) {
return
;
}
@@ -54,60 +77,77 @@ export function RichTextEditor({ value, onChange, placeholder }: RichTextEditorP
};
return (
-
-
-
toggle(() => editor.chain().focus().toggleBold().run())}
- className={cn(
- 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
- editor.isActive('bold') && 'bg-white text-slate-900'
- )}
- >
-
-
-
toggle(() => editor.chain().focus().toggleItalic().run())}
- className={cn(
- 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
- editor.isActive('italic') && 'bg-white text-slate-900'
- )}
- >
-
-
-
toggle(() => editor.chain().focus().toggleBulletList().run())}
- className={cn(
- 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
- editor.isActive('bulletList') && 'bg-white text-slate-900'
- )}
- >
-
-
-
toggle(() => editor.chain().focus().toggleOrderedList().run())}
- className={cn(
- 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
- editor.isActive('orderedList') && 'bg-white text-slate-900'
- )}
- >
-
-
-
toggle(() => editor.chain().focus().toggleBlockquote().run())}
- className={cn(
- 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
- editor.isActive('blockquote') && 'bg-white text-slate-900'
- )}
- >
-
-
+
+
+
+ toggle(() => editor.chain().focus().toggleBold().run())}
+ className={cn(
+ 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
+ editor.isActive('bold') && 'bg-white text-slate-900'
+ )}
+ >
+
+
+ toggle(() => editor.chain().focus().toggleItalic().run())}
+ className={cn(
+ 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
+ editor.isActive('italic') && 'bg-white text-slate-900'
+ )}
+ >
+
+
+ toggle(() => editor.chain().focus().toggleBulletList().run())}
+ className={cn(
+ 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
+ editor.isActive('bulletList') && 'bg-white text-slate-900'
+ )}
+ >
+
+
+ toggle(() => editor.chain().focus().toggleOrderedList().run())}
+ className={cn(
+ 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
+ editor.isActive('orderedList') && 'bg-white text-slate-900'
+ )}
+ >
+
+
+ toggle(() => editor.chain().focus().toggleBlockquote().run())}
+ className={cn(
+ 'inline-flex h-7 w-7 items-center justify-center rounded-md text-xs text-slate-600 hover:bg-white',
+ editor.isActive('blockquote') && 'bg-white text-slate-900'
+ )}
+ >
+
+
+
+
+ {/* Extra toolbar content (e.g., mic button) */}
+ {toolbarExtra && (
+
+ {toolbarExtra}
+
+ )}
);
}
+
+// Export Editor type for external use
+export type { Editor } from '@tiptap/react';
diff --git a/docs/troubleshooting/screenprint-nieuwe-rapportage-ux.png b/docs/troubleshooting/screenprint-nieuwe-rapportage-ux.png
new file mode 100644
index 0000000..996a5ee
Binary files /dev/null and b/docs/troubleshooting/screenprint-nieuwe-rapportage-ux.png differ