diff --git a/surfsense_web/app/preview/chat-comments/page.tsx b/surfsense_web/app/preview/chat-comments/page.tsx index 5e3466468..d5611202c 100644 --- a/surfsense_web/app/preview/chat-comments/page.tsx +++ b/surfsense_web/app/preview/chat-comments/page.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { CommentComposer } from "@/components/chat-comments/comment-composer/comment-composer"; import { CommentItem } from "@/components/chat-comments/comment-item/comment-item"; import type { CommentData } from "@/components/chat-comments/comment-item/types"; +import { CommentPanel } from "@/components/chat-comments/comment-panel/comment-panel"; import { CommentThread } from "@/components/chat-comments/comment-thread/comment-thread"; import type { CommentThreadData } from "@/components/chat-comments/comment-thread/types"; import { MemberMentionPicker } from "@/components/chat-comments/member-mention-picker/member-mention-picker"; @@ -182,6 +183,145 @@ const fakeThreadsData: CommentThreadData[] = [ }, ], }, + { + id: 6, + messageId: 101, + content: "I think we should also consider edge cases here. What happens if the input is empty?", + contentRendered: "I think we should also consider edge cases here. What happens if the input is empty?", + author: { + id: "550e8400-e29b-41d4-a716-446655440001", + displayName: "Alice Smith", + email: "alice@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 10800000).toISOString(), + updatedAt: new Date(Date.now() - 10800000).toISOString(), + isEdited: false, + canEdit: false, + canDelete: true, + replyCount: 3, + replies: [ + { + id: 7, + content: "Good point! We should add validation.", + contentRendered: "Good point! We should add validation.", + author: { + id: "550e8400-e29b-41d4-a716-446655440002", + displayName: "Bob Johnson", + email: "bob.johnson@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 10000000).toISOString(), + updatedAt: new Date(Date.now() - 10000000).toISOString(), + isEdited: false, + canEdit: true, + canDelete: true, + }, + { + id: 8, + content: "I'll handle the validation logic @Alice Smith", + contentRendered: "I'll handle the validation logic @Alice Smith", + author: { + id: "550e8400-e29b-41d4-a716-446655440003", + displayName: "Charlie Brown", + email: "charlie@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 9500000).toISOString(), + updatedAt: new Date(Date.now() - 9500000).toISOString(), + isEdited: false, + canEdit: false, + canDelete: true, + }, + { + id: 9, + content: "Thanks @Charlie Brown!", + contentRendered: "Thanks @Charlie Brown!", + author: { + id: "550e8400-e29b-41d4-a716-446655440001", + displayName: "Alice Smith", + email: "alice@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 9000000).toISOString(), + updatedAt: new Date(Date.now() - 9000000).toISOString(), + isEdited: false, + canEdit: false, + canDelete: true, + }, + ], + }, + { + id: 10, + messageId: 101, + content: "The performance looks great in the benchmarks. Nice work everyone!", + contentRendered: "The performance looks great in the benchmarks. Nice work everyone!", + author: { + id: "550e8400-e29b-41d4-a716-446655440005", + displayName: "Emma Davis", + email: "emma@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 14400000).toISOString(), + updatedAt: new Date(Date.now() - 14400000).toISOString(), + isEdited: false, + canEdit: false, + canDelete: true, + replyCount: 0, + replies: [], + }, + { + id: 11, + messageId: 101, + content: "Should we schedule a review meeting for this?", + contentRendered: "Should we schedule a review meeting for this?", + author: { + id: "550e8400-e29b-41d4-a716-446655440004", + displayName: null, + email: "david.wilson@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 18000000).toISOString(), + updatedAt: new Date(Date.now() - 18000000).toISOString(), + isEdited: true, + canEdit: true, + canDelete: true, + replyCount: 2, + replies: [ + { + id: 12, + content: "Yes, let's do it tomorrow at 10am", + contentRendered: "Yes, let's do it tomorrow at 10am", + author: { + id: "550e8400-e29b-41d4-a716-446655440002", + displayName: "Bob Johnson", + email: "bob.johnson@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 17000000).toISOString(), + updatedAt: new Date(Date.now() - 17000000).toISOString(), + isEdited: false, + canEdit: true, + canDelete: true, + }, + { + id: 13, + content: "Works for me!", + contentRendered: "Works for me!", + author: { + id: "550e8400-e29b-41d4-a716-446655440005", + displayName: "Emma Davis", + email: "emma@example.com", + avatarUrl: null, + }, + createdAt: new Date(Date.now() - 16000000).toISOString(), + updatedAt: new Date(Date.now() - 16000000).toISOString(), + isEdited: false, + canEdit: false, + canDelete: true, + }, + ], + }, ]; export default function ChatCommentsPreviewPage() { @@ -203,7 +343,8 @@ export default function ChatCommentsPreviewPage() {

Comment Composer

- Type @ to trigger mention picker. Use Tab/Shift+Tab/Arrow keys to navigate, Enter to select. + Type @ to trigger mention picker. Use Tab/Shift+Tab/Arrow keys to navigate, Enter to + select.

@@ -226,11 +367,62 @@ export default function ChatCommentsPreviewPage() { )}
+ {/* Comment Panel Section */} +
+

Comment Panel

+

+ Full panel with scrollable threads and composer. Shows alongside AI responses. +

+ +
+
+

With comments

+ alert(`Create: ${content}`)} + onCreateReply={(id, content) => alert(`Reply ${id}: ${content}`)} + onEditComment={(id) => alert(`Edit ${id}`)} + onDeleteComment={(id) => alert(`Delete ${id}`)} + /> +
+ +
+

Empty state

+ alert(`Create: ${content}`)} + onCreateReply={(id, content) => alert(`Reply ${id}: ${content}`)} + onEditComment={(id) => alert(`Edit ${id}`)} + onDeleteComment={(id) => alert(`Delete ${id}`)} + /> +
+ +
+

Loading

+ {}} + onCreateReply={() => {}} + onEditComment={() => {}} + onDeleteComment={() => {}} + /> +
+
+
+ {/* Comment Thread Section */}

Comment Thread

- Two top-level comments with replies. Click Reply to open composer. Click the replies count to collapse/expand. + Two top-level comments with replies. Click Reply to open composer. Click the replies + count to collapse/expand.

@@ -266,7 +458,9 @@ export default function ChatCommentsPreviewPage() { {/* Member Mention Picker Section */}
-

Member Mention Picker (Standalone)

+

+ Member Mention Picker (Standalone) +

diff --git a/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx b/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx new file mode 100644 index 000000000..81de6fb76 --- /dev/null +++ b/surfsense_web/components/chat-comments/comment-panel/comment-panel.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { MessageSquarePlus } from "lucide-react"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { CommentComposer } from "../comment-composer/comment-composer"; +import { CommentThread } from "../comment-thread/comment-thread"; +import type { CommentPanelProps } from "./types"; + +export function CommentPanel({ + threads, + members, + membersLoading = false, + isLoading = false, + onCreateComment, + onCreateReply, + onEditComment, + onDeleteComment, + isSubmitting = false, + maxHeight = 400, +}: CommentPanelProps) { + const [isComposerOpen, setIsComposerOpen] = useState(false); + + const handleCommentSubmit = (content: string) => { + onCreateComment(content); + setIsComposerOpen(false); + }; + + const handleComposerCancel = () => { + setIsComposerOpen(false); + }; + + if (isLoading) { + return ( +
+
+
+ Loading comments... +
+
+ ); + } + + const hasThreads = threads.length > 0; + + return ( +
+ {hasThreads ? ( +
+
+ {threads.map((thread) => ( + + ))} +
+
+ ) : ( +
+ +

No comments yet

+

+ Start a conversation about this response +

+
+ )} + +
+ {isComposerOpen ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/surfsense_web/components/chat-comments/comment-panel/types.ts b/surfsense_web/components/chat-comments/comment-panel/types.ts new file mode 100644 index 000000000..d2239b34f --- /dev/null +++ b/surfsense_web/components/chat-comments/comment-panel/types.ts @@ -0,0 +1,16 @@ +import type { CommentThreadData } from "../comment-thread/types"; +import type { MemberOption } from "../member-mention-picker/types"; + +export interface CommentPanelProps { + messageId: number; + threads: CommentThreadData[]; + members: MemberOption[]; + membersLoading?: boolean; + isLoading?: boolean; + onCreateComment: (content: string) => void; + onCreateReply: (commentId: number, content: string) => void; + onEditComment: (commentId: number, content: string) => void; + onDeleteComment: (commentId: number) => void; + isSubmitting?: boolean; + maxHeight?: number; +}