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
32 changes: 21 additions & 11 deletions src/app/(user-page)/my-meeting/_features/CardRightSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
isPublic,
className,
meetingId,
showPublicSelect = false,
}: {
memberList: Member[];
isPublic: boolean;
className?: string;
meetingId: number;
showPublicSelect?: boolean;
}) => {
const [isUserListModalOpen, setIsUserListModalOpen] = useState(false);
const handleConfirm = () => {
Expand Down Expand Up @@ -105,7 +107,7 @@
// 프로필 보기 할 유저
const [selectedUser, setSelectedUser] = useState<Member | null>(null);

const { data: currentUser, isLoading, error } = useBannerQueries();

Check warning on line 110 in src/app/(user-page)/my-meeting/_features/CardRightSection.tsx

View workflow job for this annotation

GitHub Actions / check

'error' is assigned a value but never used. Allowed unused vars must match /^_/u
if (isLoading || !currentUser) {
return;
}
Expand All @@ -123,7 +125,7 @@
}}
>
<div className="hidden flex-col justify-center gap-[16px] lg:flex">
<h3 className="typo-head3 text-main">참가 중인 멤버</h3>
<h3 className="typo-head3 text-main">참가 중인 멤버</h3>
<div className="h-[172px] overflow-y-auto">
{memberList.map((member: Member) => (
<div key={member.userId} className="flex items-center py-[8px]">
Expand All @@ -132,14 +134,16 @@
alt="맴버 프로필"
width={40}
height={40}
className="rounded-[9.92px] "
className="rounded-[9.92px]"
/>
<p className="typo-head3 w-[114px] p-[6px] text-Cgray700">
{member.name}
</p>
{member.userId !== currentUser?.userId && (
<div className="flex h-[40px] gap-[6px]">
<Tag variant={member.memberStatus} className="w-[49px]" />
{showPublicSelect && (
<Tag variant={member.memberStatus} className="w-[49px]" />
)}
<Button
variant={'outline'}
className="h-[40px] w-[93px]"
Expand All @@ -155,14 +159,19 @@
</div>
</div>

<Button
variant="outline"
className="flex h-[40px] flex-1 md:h-[46px] lg:hidden"
onClick={() => setIsUserListModalOpen(true)}
>
맴버 명단 보기
</Button>
<PublicSelect isPublic={isPublic} meetingId={meetingId} />
<div className="flex flex-1 gap-[16px]">
<Button
variant="outline"
className="flex h-[40px] flex-1 md:h-[46px] lg:hidden"
onClick={() => setIsUserListModalOpen(true)}
>
맴버 명단 보기
</Button>
{showPublicSelect && (
<PublicSelect isPublic={isPublic} meetingId={meetingId} />
)}
</div>

<Modal
isOpen={isUserProfileModalOpen}
onClose={handleSecondModalCancel}
Expand Down Expand Up @@ -202,6 +211,7 @@
setIsUserListModalOpen={setIsUserListModalOpen}
currentUser={currentUser}
handlePrefetchProfile={handlePrefetchProfile}
showPublicSelect={showPublicSelect}
/>
</Modal>
</div>
Expand Down
9 changes: 6 additions & 3 deletions src/app/(user-page)/my-meeting/_features/ModalUserList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Tag } from '@/components/ui/Tag';
import { useBannerQueries } from '@/hooks/queries/useMyPageQueries';
import Image from 'next/image';
import React from 'react';
import { Dispatch, SetStateAction, useState } from 'react';
import { Dispatch, SetStateAction } from 'react';
import type { IBanner, Member } from 'types/myMeeting';

import { Button } from '../../../../components/ui/Button';
Expand All @@ -15,6 +14,7 @@ const ModalUserList = ({
currentUser,
className,
handlePrefetchProfile,
showPublicSelect = false,
}: {
memberList: Member[];
setSelectedUser: Dispatch<React.SetStateAction<Member | null>>;
Expand All @@ -23,6 +23,7 @@ const ModalUserList = ({
currentUser: IBanner;
className?: string;
handlePrefetchProfile: (member: Member) => Promise<void>;
showPublicSelect?: boolean;
}) => {
const handleProfileClick = (user: Member) => {
setSelectedUser(user);
Expand Down Expand Up @@ -51,7 +52,9 @@ const ModalUserList = ({
</div>
{user.userId !== currentUser?.userId && (
<div className="flex gap-[6px]">
<Tag variant={user.memberStatus} className="w-[49px]" />
{showPublicSelect && (
<Tag variant={user.memberStatus} className="w-[49px]" />
)}
<div>
<Button
onClick={() => handleProfileClick(user)}
Expand Down
160 changes: 160 additions & 0 deletions src/app/(user-page)/my-meeting/_features/Participated.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use client';

import HorizonCard from '@/components/ui/HorizonCard';
import useInfiniteScroll from '@/hooks/common/useInfiniteScroll';
import { useInfiniteMyMeetingParticipatedQueries } from '@/hooks/queries/useMyMeetingQueries';
import { useRouter } from 'next/navigation';

import CardRightSection from './CardRightSection';
import MeetingListSkeleton from './skeletons/SkeletonMeetingList';

const Participated = () => {
const router = useRouter();

// 참여한 모임 데이터 가져오기
const {
data: meetingData,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useInfiniteMyMeetingParticipatedQueries();

// 무한 스크롤을 위한 ref
const lastMeetingRef = useInfiniteScroll({
fetchNextPage,
isFetchingNextPage,
hasNextPage: !!hasNextPage,
});

// 로딩 중인 경우 스켈레톤 UI 표시
if (isLoading || !meetingData) {
return <MeetingListSkeleton />;
}

// 상세 페이지로 이동하는 핸들러
const handleMoveDetailPage = (meetingId: number) => {
/**
* TODO
* 추후 category 수정
*/
router.push(`/meeting/study/${meetingId}`);
};

return (
<div>
{meetingData.pages.map((page, pageIdx) => (
<div key={pageIdx}>
{page.content.map((meeting) => (
<div key={meeting.meetingId}>
{/* 데스크탑 */}
<div className="hidden border-b border-Cgray300 py-[42px] lg:flex">
<HorizonCard
onClick={() => handleMoveDetailPage(meeting.meetingId)}
key={meeting.meetingId}
title={meeting.title}
thumbnailUrl={meeting.thumbnail}
location={meeting.location}
total={meeting.maxMember}
value={meeting.memberCount}
className="flex-row"
meetingId={meeting.meetingId}
category={''}
>
<CardRightSection
memberList={meeting.memberList}
isPublic={meeting.isPublic}
className="hidden lg:flex"
meetingId={meeting.meetingId}
/>
</HorizonCard>
</div>

{/* 태블릿 */}
<div className="hidden flex-col border-b border-Cgray300 py-[42px] md:flex lg:hidden">
<HorizonCard
onClick={() => handleMoveDetailPage(meeting.meetingId)}
key={meeting.meetingId}
title={meeting.title}
thumbnailUrl={meeting.thumbnail}
location={meeting.location}
total={meeting.maxMember}
value={meeting.memberCount}
thumbnailHeight={160}
thumbnailWidth={160}
className=""
meetingId={meeting.meetingId}
category={''}
/>
<CardRightSection
memberList={meeting.memberList}
isPublic={meeting.isPublic}
className="flex lg:hidden"
meetingId={meeting.meetingId}
/>
</div>

{/* 모바일 */}
<div className="flex flex-col border-b border-Cgray300 py-[42px] md:hidden">
<HorizonCard
onClick={() => handleMoveDetailPage(meeting.meetingId)}
key={meeting.meetingId}
title={meeting.title}
thumbnailUrl={meeting.thumbnail}
location={meeting.location}
total={meeting.maxMember}
value={meeting.memberCount}
thumbnailHeight={80}
thumbnailWidth={80}
className=""
meetingId={meeting.meetingId}
category={''}
/>
<CardRightSection
memberList={meeting.memberList}
isPublic={meeting.isPublic}
className="flex lg:hidden"
meetingId={meeting.meetingId}
/>
</div>
</div>
))}
</div>
))}

{/* 무한 스크롤을 위한 별도의 Observer 요소 */}
{hasNextPage && (
<div
ref={lastMeetingRef}
className="h-20 w-full"
id="infinite-scroll-trigger"
/>
)}

{/* 추가 데이터 로딩 중 표시 */}
{isFetchingNextPage && (
<div className="flex justify-center py-4">
<div className="text-gray-500 animate-pulse text-white">
로딩 중...
</div>
</div>
)}

{/* 데이터가 없는 경우 표시 */}
{meetingData.pages[0].content.length === 0 && (
<div className="text-gray-500 py-8 text-center text-white">
참여한 모임이 없습니다.
</div>
)}

{/* 더 이상 데이터가 없음을 표시 */}
{!hasNextPage && meetingData.pages[0].content.length > 0 && (
<div className="text-gray-500 py-4 text-center text-white">
모든 모임을 불러왔습니다.
</div>
)}
</div>
);
};

export default Participated;
19 changes: 14 additions & 5 deletions src/app/(user-page)/my-meeting/my/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import NotYet from '@/components/common/NotYet';
import { myMeetingKeys } from '@/hooks/queries/useMyMeetingQueries';
import { QUERY_KEYS } from '@/hooks/queries/useMyPageQueries';
import {
HydrationBoundary,
QueryClient,
dehydrate,
} from '@tanstack/react-query';
import { getMyMeetingManage } from 'service/api/mymeeting';
import {
getMyMeetingManage,
getMyMeetingParticipated,
} from 'service/api/mymeeting';
import { getBanner } from 'service/api/mypageProfile';
import { Paginated } from 'types/meeting';
import { IMyMeetingManage } from 'types/myMeeting';

import Created from '../_features/Created';
// import Joined from '../../components/Joined';
import Participated from '../_features/Participated';
import Tab from '../_features/Tab';

export default async function Page({
Expand Down Expand Up @@ -40,7 +42,14 @@ export default async function Page({
initialPageParam: 0,
});
} else {
// 참여 중인 모임 prefetch
// 내가 참여하고있는 모임 prefetch
await queryClient.prefetchInfiniteQuery({
queryKey: myMeetingKeys.participated(),
queryFn: ({ pageParam }) => getMyMeetingParticipated(pageParam),
getNextPageParam: (lastPage: Paginated<IMyMeetingManage>) =>
lastPage.nextCursor ?? false,
initialPageParam: 0,
});
}

return (
Expand All @@ -49,7 +58,7 @@ export default async function Page({
<Tab type={type} />
</div>
<HydrationBoundary state={dehydrate(queryClient)}>
{type === 'created' ? <Created /> : <NotYet />}
{type === 'created' ? <Created /> : <Participated />}
</HydrationBoundary>
</div>
);
Expand Down
16 changes: 14 additions & 2 deletions src/hooks/queries/useMyMeetingQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {
getMyMeetingManage,
getMyMeetingMemberProfile,
} from 'service/api/mymeeting';
import { getMyMeetingParticipated } from 'service/api/mymeeting';
import { Paginated } from 'types/meeting';
import { IMyMeetingManage } from 'types/myMeeting';

export const myMeetingKeys = {
all: ['mymeeting'] as const,
manage: () => [...myMeetingKeys.all, 'manage'] as const,
participated: () => [...myMeetingKeys.all, 'participated'] as const,
memberProfile: (meetingId: number, userId: number) => [
...myMeetingKeys.all,
'profile',
Expand All @@ -21,8 +23,18 @@ export const useInfiniteMyMeetingManageQueries = () => {
queryKey: myMeetingKeys.manage(),
queryFn: ({ pageParam }) => getMyMeetingManage(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage: Paginated<IMyMeetingManage>, pages) => {
console.log('[mutation] lastPage: ', lastPage);
getNextPageParam: (lastPage: Paginated<IMyMeetingManage>) => {
return lastPage.nextCursor ?? null;
},
});
};

export const useInfiniteMyMeetingParticipatedQueries = () => {
return useInfiniteQuery({
queryKey: myMeetingKeys.participated(),
queryFn: ({ pageParam }) => getMyMeetingParticipated(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage: Paginated<IMyMeetingManage>) => {
return lastPage.nextCursor ?? null;
},
});
Expand Down
Loading
Loading