import React, { useState, useRef, useCallback, memo, useMemo } from "react"; import { useParams } from "react-router-dom"; import { ActionIcon, Center, Divider, Group, Paper, Stack, Tabs, Badge, Text, ScrollArea, } from "@mantine/core"; import CommentListItem from "@/features/comment/components/comment-list-item"; import { useCommentsQuery, useCreateCommentMutation, } from "@/features/comment/queries/comment-query"; import CommentEditor from "@/features/comment/components/comment-editor"; import CommentActions from "@/features/comment/components/comment-actions"; import { useFocusWithin } from "@mantine/hooks"; import { IComment } from "@/features/comment/types/comment.types.ts"; import { usePageQuery } from "@/features/page/queries/page-query.ts"; import { IPagination } from "@/lib/types.ts"; import { extractPageSlugId } from "@/lib"; import { useTranslation } from "react-i18next"; import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts"; import { IconArrowUp, IconMessageOff } from "@tabler/icons-react"; function CommentListWithTabs() { const { t } = useTranslation(); const { pageSlug } = useParams(); const { data: page } = usePageQuery({ pageId: extractPageSlugId(pageSlug) }); const { data: comments, isLoading: isCommentsLoading, isError, } = useCommentsQuery({ pageId: page?.id }); const createCommentMutation = useCreateCommentMutation(); const [isLoading, setIsLoading] = useState(false); const { data: space } = useGetSpaceBySlugQuery(page?.space?.slug); const canComment = page?.permissions?.canEdit ?? false; // Separate active and resolved comments const { activeComments, resolvedComments } = useMemo(() => { if (!comments?.items) { return { activeComments: [], resolvedComments: [] }; } const parentComments = comments.items.filter( (comment: IComment) => comment.parentCommentId === null, ); const active = parentComments.filter( (comment: IComment) => !comment.resolvedAt, ); const resolved = parentComments.filter( (comment: IComment) => comment.resolvedAt, ); return { activeComments: active, resolvedComments: resolved }; }, [comments]); const [isPageCommentLoading, setIsPageCommentLoading] = useState(false); const handleAddPageComment = useCallback( async (_commentId: string, content: string) => { try { setIsPageCommentLoading(true); const createdComment = await createCommentMutation.mutateAsync({ pageId: page?.id, content: JSON.stringify(content), }); setTimeout(() => { const selector = `div[data-comment-id="${createdComment.id}"]`; const commentElement = document.querySelector(selector); commentElement?.scrollIntoView({ behavior: "smooth", block: "center", }); }, 400); } catch (error) { console.error("Failed to post comment:", error); } finally { setIsPageCommentLoading(false); } }, [createCommentMutation, page?.id], ); const handleAddReply = useCallback( async (commentId: string, content: string) => { try { setIsLoading(true); const commentData = { pageId: page?.id, parentCommentId: commentId, content: JSON.stringify(content), }; await createCommentMutation.mutateAsync(commentData); } catch (error) { console.error("Failed to post comment:", error); } finally { setIsLoading(false); } }, [createCommentMutation, page?.id], ); const renderComments = useCallback( (comment: IComment) => (
{!comment.resolvedAt && canComment && ( <> )}
), [comments, handleAddReply, isLoading, space?.membership?.role], ); if (isCommentsLoading) { return <>; } if (isError) { return
{t("Error loading comments.")}
; } const totalComments = activeComments.length + resolvedComments.length; const pageCommentInput = canComment ? ( ) : null; return (
{activeComments.length} } > {t("Open")} {resolvedComments.length} } > {t("Resolved")}
{activeComments.length === 0 ? (
{t("No open comments.")}
) : ( activeComments.map(renderComments) )}
{resolvedComments.length === 0 ? (
{t("No resolved comments.")}
) : ( resolvedComments.map(renderComments) )}
{pageCommentInput}
); } interface ChildCommentsProps { comments: IPagination; parentId: string; pageId: string; canComment: boolean; userSpaceRole?: string; } const ChildComments = ({ comments, parentId, pageId, canComment, userSpaceRole, }: ChildCommentsProps) => { const getChildComments = useCallback( (parentId: string) => comments.items.filter( (comment: IComment) => comment.parentCommentId === parentId, ), [comments.items], ); return (
{getChildComments(parentId).map((childComment) => (
))}
); }; const MemoizedChildComments = memo(ChildComments); const CommentEditorWithActions = ({ commentId, onSave, isLoading, placeholder = undefined, }) => { const [content, setContent] = useState(""); const { ref, focused } = useFocusWithin(); const commentEditorRef = useRef(null); const handleSave = useCallback(() => { onSave(commentId, content); setContent(""); commentEditorRef.current?.clearContent(); }, [commentId, content, onSave]); return (
{focused && }
); }; const PageCommentInput = ({ onSave, isLoading }) => { const { t } = useTranslation(); const [content, setContent] = useState(""); const { ref, focused } = useFocusWithin(); const commentEditorRef = useRef(null); const handleSave = useCallback(() => { onSave(null, content); setContent(""); commentEditorRef.current?.clearContent(); }, [content, onSave]); return (
{focused && ( )}
); }; export default CommentListWithTabs;