Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/features/study/group/channel/api/get-comments.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { axiosInstance } from '@/api/client/axios';
import { GetCommentsRequest, GetCommentsResponse } from './types';
import { GetCommentsRequest, PaginatedCommentsResponse } from './types';

export const getComments = async (
param: GetCommentsRequest,
): Promise<GetCommentsResponse[]> => {
): Promise<PaginatedCommentsResponse> => {
const { groupStudyId, threadId } = param;

try {
Expand All @@ -12,12 +12,12 @@ export const getComments = async (
);

if (data.statusCode !== 200) {
throw new Error('Failed to fetch post');
throw new Error('Failed to fetch comments');
}

return data.content.content;
return data.content;
} catch (err) {
console.error('Error fetching post:', err);
console.error('Error fetching comments:', err);
throw err;
}
};
13 changes: 7 additions & 6 deletions src/features/study/group/channel/api/get-threads.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { axiosInstance } from '@/api/client/axios';
import { GetThreadsRequest, GetThreadsResponse } from './types';
import { GetThreadsRequest, PaginatedThreadsResponse } from './types';

export const getThreads = async (
param: GetThreadsRequest,
): Promise<GetThreadsResponse[]> => {
const { groupStudyId } = param;
): Promise<PaginatedThreadsResponse> => {
const { groupStudyId, page = 0, size = 10 } = param;

try {
const { data } = await axiosInstance.get(
`group-studies/${groupStudyId}/threads`,
{ params: { page, size } },
);

if (data.statusCode !== 200) {
throw new Error('Failed to fetch post');
throw new Error('Failed to fetch threads');
}

return data.content.content;
return data.content;
} catch (err) {
console.error('Error fetching post:', err);
console.error('Error fetching threads:', err);
throw err;
}
};
17 changes: 16 additions & 1 deletion src/features/study/group/channel/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,16 @@ export interface DeleteCommentRequest extends GetCommentsRequest {
commentId: number;
}

export type GetThreadsRequest = GroupStudyIdParam;
export interface GetThreadsRequest extends GroupStudyIdParam {
page?: number;
size?: number;
}

export interface PaginatedThreadsResponse {
content: GetThreadsResponse[];
totalElements: number;
totalPages: number;
}

export interface PostThreadRequest extends GroupStudyIdParam {
content: string;
Expand Down Expand Up @@ -76,3 +85,9 @@ export interface GetPostResponse {
export interface GetCommentsResponse extends GetThreadsResponse {
commentId: number;
}

export interface PaginatedCommentsResponse {
content: GetCommentsResponse[];
totalElements: number;
totalPages: number;
}
21 changes: 16 additions & 5 deletions src/features/study/group/channel/model/use-channel-query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query';
import { deleteComment } from '../api/delete-comment';
import { deleteThread } from '../api/delete-thread';
import { getComments } from '../api/get-comments';
Expand Down Expand Up @@ -31,11 +31,22 @@ export const usePostQuery = (groupStudyId: number) => {
};

// thread
export const useThreadsQuery = (groupStudyId: number) => {
interface UseThreadsQueryParams {
groupStudyId: number;
page?: number;
size?: number;
}

export const useThreadsQuery = ({
groupStudyId,
page = 1,
size = 10,
}: UseThreadsQueryParams) => {
return useQuery({
queryKey: ['get-threads', groupStudyId],
queryFn: () => getThreads({ groupStudyId }),
enabled: !!groupStudyId, // id가 존재할 때만 실행
queryKey: ['get-threads', groupStudyId, page, size],
queryFn: () => getThreads({ groupStudyId, page: page - 1, size }),
enabled: !!groupStudyId,
placeholderData: keepPreviousData,
});
};

Expand Down
77 changes: 70 additions & 7 deletions src/features/study/group/channel/ui/comment-section.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,54 @@
import { useQueries } from '@tanstack/react-query';
import { MessageCircle } from 'lucide-react';
import { useState } from 'react';
import Pagination from '@/components/ui/pagination';
import Comment from './comment';
import CommentInput from './comment-input';
import SubComments from './sub-comments';
import { ThreadReaction } from './thread-reaction';
import { getComments } from '../api/get-comments';
import {
usePostThreadMutation,
useThreadsQuery,
} from '../model/use-channel-query';

const COMMENTS_PAGE_SIZE = 10;

interface CommentProps {
groupStudyId: number;
}

export default function CommentSection({ groupStudyId }: CommentProps) {
const [page, setPage] = useState<number>(1);
const {
data,
isLoading,
isError,
refetch: threadRefetch,
} = useThreadsQuery(groupStudyId);
} = useThreadsQuery({ groupStudyId, page, size: COMMENTS_PAGE_SIZE });

const [threadText, setThreadText] = useState<string>('');
const [openThreadId, setOpenThreadId] = useState<number | null>(null); // 👈 변경
const [openThreadId, setOpenThreadId] = useState<number | null>(null);

// 모든 스레드의 답글을 병렬로 조회
const commentQueries = useQueries({
queries: (data?.content ?? []).map((thread) => ({
queryKey: ['comments', groupStudyId, thread.threadId],
queryFn: () => getComments({ groupStudyId, threadId: thread.threadId }),
enabled: !!data,
})),
});

// 모든 답글 쿼리 로딩 완료 여부
const allCommentsLoaded = commentQueries.every((q) => !q.isLoading);

// 총 댓글 수 (메인 댓글 + 답글)
const totalReplyCount = commentQueries.reduce(
(sum, q) => sum + (q.data?.totalElements ?? 0),
0,
);
console.log(data.content, 'data');
const totalCommentCount = (data?.totalElements ?? 0) + totalReplyCount;

const { mutate: createThread } = usePostThreadMutation();

Expand All @@ -38,20 +65,47 @@ export default function CommentSection({ groupStudyId }: CommentProps) {
);
};

if (isLoading) return null; // 👈 안전 반환
// threads 로딩 중이거나 답글 로딩 중이면 대기
if (isLoading || !allCommentsLoaded) return null;

if (isError) {
return (
<div className="flex flex-col items-center gap-200 py-400">
<span className="text-text-subtlest">
댓글을 불러오는 중 오류가 발생했습니다.
</span>
<button
onClick={() => threadRefetch()}
className="text-text-accent-blue hover:underline"
>
다시 시도
</button>
</div>
);
}

const hasComments = (data?.content?.length ?? 0) > 0;

return (
<div className="flex flex-col gap-400">
<div className="flex items-center">
<MessageCircle className="mr-100 inline-block" size={24} />
<span className="font-designer-16b text-text-strong mr-50">댓글</span>
<span className="font-designer-16b text-text-subtlest">
{data?.length ?? 0}개
{totalCommentCount}개
</span>
</div>

<div className="flex flex-col gap-300">
{data?.map((comment) => {
{!hasComments && (
<div className="flex justify-center py-400">
<span className="text-text-subtlest">
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!
</span>
</div>
)}

{data?.content?.map((comment) => {
const isOpen = openThreadId === comment.threadId;
const toggle = () =>
setOpenThreadId((prev) =>
Expand Down Expand Up @@ -89,8 +143,8 @@ export default function CommentSection({ groupStudyId }: CommentProps) {
<SubComments
threadId={comment.threadId}
groupStudyId={groupStudyId}
showInput={isOpen} // 👈 스레드별
handleShowInput={toggle} // 👈 스레드별
showInput={isOpen}
handleShowInput={toggle}
/>
</div>
</div>
Expand All @@ -104,6 +158,15 @@ export default function CommentSection({ groupStudyId }: CommentProps) {
onConfirm={() => handleThreadSubmit(groupStudyId, threadText)}
onCancel={() => setThreadText('')}
/>

{(data?.totalPages ?? 0) > 1 && (
<Pagination
page={page}
totalPages={data?.totalPages ?? 1}
onChangePage={setPage}
className="mt-400"
/>
)}
</div>
</div>
);
Expand Down
24 changes: 23 additions & 1 deletion src/features/study/group/channel/ui/sub-comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useCommentsQuery,
usePostCommentMutation,
} from '../model/use-channel-query';
import Button from '@/components/ui/button';

interface SubCommentsProps {
threadId: number;
Expand All @@ -24,9 +25,12 @@ export default function SubComments({
const {
data,
isLoading,
isError,
refetch: commentsRefetch,
} = useCommentsQuery(groupStudyId, threadId);

console.log(data, 'subcomment');

const [commentText, setCommentText] = useState('');

const { mutate: createComment } = usePostCommentMutation();
Expand All @@ -48,9 +52,27 @@ export default function SubComments({

if (isLoading) return null;

if (isError) {
return (
<div className="flex items-center gap-100 py-200">
<span className="text-text-subtlest text-sm">
답글을 불러오지 못했습니다.
</span>
<Button
size="small"
color="outlined"
onClick={() => commentsRefetch()}
className="text-text-accent-blue font-designer-14r text-sm hover:underline"
>
다시 시도
</Button>
</div>
);
}

return (
<div className="flex flex-col gap-300">
{data.map((subComment) => (
{data?.content?.map((subComment) => (
<div
key={subComment.commentId}
className="flex flex-col gap-200 pt-300"
Expand Down
Loading