Skip to content

Commit 3fa083e

Browse files
authored
Merge pull request #149 from CodeitFESI4-Team1/Fix/146/GatheringDetail
Fix/146/gathering detail
2 parents 43fbb60 + f356715 commit 3fa083e

File tree

18 files changed

+157
-96
lines changed

18 files changed

+157
-96
lines changed

src/_queries/gathering/gathering-detail-queries.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
33
import { GetGatheringDetail, createGathering } from '@/src/_apis/gathering/gathering-detail-apis';
44
import { CreateGatheringRequestTypes } from '@/src/types/gathering-data';
55

6-
export function useGetGatheringDetailQuery(crewId: number, gatheringId: number) {
6+
export function useGetGatheringDetailQuery(
7+
crewId: number,
8+
gatheringId: number,
9+
options?: { enabled?: boolean },
10+
) {
711
return useQuery({
812
queryKey: ['gatheringDetail', crewId, gatheringId],
913
queryFn: () => GetGatheringDetail(crewId, gatheringId),
14+
...options,
1015
});
1116
}
1217

src/app/(crew)/crew/create/_components/create-crew-form/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,13 @@ export default function CreateCrewForm({
287287
htmlFor="crew-totalCount"
288288
className="text-base font-semibold text-gray-800 md:text-xl"
289289
>
290-
모집 정원을 선택해주세요.
290+
크루 최대 인원을 선택해주세요.
291291
</label>
292292
<Controller
293293
name="totalCount"
294294
control={control}
295295
rules={{
296-
required: '모집 정원을 입력해주세요.',
296+
required: '크루 최대 인원을 입력해주세요.',
297297
}}
298298
render={({ field }) => (
299299
<NumberInput

src/app/(crew)/crew/detail/[id]/_components/create-gathering/index.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22

33
import { useRouter } from 'next/navigation';
44
import { useDisclosure } from '@mantine/hooks';
5+
import { useGetCrewDetailQuery } from '@/src/_queries/crew/crew-detail-queries';
6+
import { useGetGatheringListQuery } from '@/src/_queries/crew/gathering-list-queries';
57
import { useAuth } from '@/src/hooks/use-auth';
68
import CreateGatheringModalContainer from '@/src/app/(crew)/crew/detail/[id]/_components/create-gathering/create-gathering-modal/container';
79
import Button from '@/src/components/common/input/button';
10+
import { CrewDetail } from '@/src/types/crew-card';
811
import { CreateGatheringFormTypes } from '@/src/types/gathering-data';
912

1013
export default function CreateGathering({ crewId }: { crewId: number }) {
1114
const { isAuth } = useAuth();
1215
const router = useRouter();
1316
const [opened, { open, close }] = useDisclosure(false);
1417

18+
const { data: gatheringList, isLoading, error, refetch } = useGetGatheringListQuery(crewId);
19+
20+
// totalGatheringCount 추출
21+
const totalGatheringCount = gatheringList?.length ?? 0;
22+
1523
const handleButtonClick = () => {
1624
if (isAuth) {
1725
open(); // 로그인 상태일 경우 모달 열기
@@ -30,7 +38,13 @@ export default function CreateGathering({ crewId }: { crewId: number }) {
3038
};
3139

3240
return (
33-
<>
41+
<div className="flex items-center justify-between px-3 md:px-7 lg:px-11">
42+
<div className="flex items-end space-x-2">
43+
<h2 className="text-2xl font-semibold text-gray-800">약속 잡기</h2>
44+
<span className="text-base font-semibold text-blue-500">
45+
현재 {totalGatheringCount}개의 약속이 개설되어 있습니다.
46+
</span>
47+
</div>
3448
<Button type="button" className="btn-filled px-4" onClick={handleButtonClick}>
3549
약속 만들기
3650
</Button>
@@ -40,6 +54,6 @@ export default function CreateGathering({ crewId }: { crewId: number }) {
4054
close={close}
4155
data={initialValue}
4256
/>
43-
</>
57+
</div>
4458
);
4559
}

src/app/(crew)/crew/detail/[id]/_components/detail-crew-container.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useState } from 'react';
44
import { toast } from 'react-toastify';
55
import { useRouter } from 'next/navigation';
6+
import { Loader } from '@mantine/core';
67
import { useDisclosure } from '@mantine/hooks';
78
import { cancelCrew, joinCrew, leaveCrew } from '@/src/_apis/crew/crew-detail-apis';
89
import { useUser } from '@/src/_queries/auth/user-queries';
@@ -20,6 +21,7 @@ export default function DetailCrew({ id }: DetailCrewContainerProps) {
2021
const [isCaptain, setIsCaptain] = useState(false);
2122
const [isMember, setIsMember] = useState(false);
2223
const [isJoining, setIsJoining] = useState(false);
24+
const [isConfirmed, setIsConfirmed] = useState(false);
2325
const [confirmCancelOpened, { open: openConfirmCancel, close: closeConfirmCancel }] =
2426
useDisclosure();
2527
const router = useRouter();
@@ -35,12 +37,20 @@ export default function DetailCrew({ id }: DetailCrewContainerProps) {
3537
const { data, isLoading, error: fetchError, refetch } = useGetCrewDetailQuery(id);
3638

3739
useEffect(() => {
38-
if (currentUserId && data) {
39-
const captain = data.crewMembers.find((member) => member.captain);
40-
const memberExists = data.crewMembers.some((member) => member.id === currentUserId);
40+
if (data) {
41+
// confirmed 상태 계산
42+
if (data.participantCount !== undefined && data.totalCount !== undefined) {
43+
setIsConfirmed(data.participantCount === data.totalCount);
44+
}
45+
46+
// Captain 및 멤버 여부 확인 (currentUserId 필요)
47+
if (currentUserId) {
48+
const captain = data.crewMembers.find((member) => member.captain);
49+
const memberExists = data.crewMembers.some((member) => member.id === currentUserId);
4150

42-
setIsCaptain(captain?.id === currentUserId);
43-
setIsMember(memberExists);
51+
setIsCaptain(captain?.id === currentUserId);
52+
setIsMember(memberExists);
53+
}
4454
}
4555
}, [currentUserId, data]);
4656

@@ -88,7 +98,7 @@ export default function DetailCrew({ id }: DetailCrewContainerProps) {
8898
toast.success('크루가 성공적으로 삭제되었습니다.');
8999
router.push('/');
90100
} catch (deleteError) {
91-
toast.error('크루 삭제 중 에러가 발생했습니다.');
101+
toast.error('🚫 크루 삭제 중 에러가 발생했습니다.');
92102
}
93103
};
94104

@@ -106,9 +116,10 @@ export default function DetailCrew({ id }: DetailCrewContainerProps) {
106116

107117
// TODO: 로딩, 에러처리 추후 개선
108118
if (isLoading) {
109-
return <p>Loading...</p>;
119+
return <Loader />;
110120
}
111121

122+
// TODO: 추후 404페이지로 이동시키기
112123
if (fetchError) {
113124
if (fetchError instanceof ApiError) {
114125
try {
@@ -118,7 +129,7 @@ export default function DetailCrew({ id }: DetailCrewContainerProps) {
118129
return <p>크루 정보를 찾을 수 없습니다</p>;
119130
}
120131
} catch (parseError) {
121-
return <p>{`Error ${fetchError.status}: ${fetchError.message}`}</p>;
132+
return <p>{`Error ${fetchError.message}`}</p>;
122133
}
123134
}
124135
return <p>데이터 통신에 실패했습니다.</p>;
@@ -135,6 +146,7 @@ export default function DetailCrew({ id }: DetailCrewContainerProps) {
135146
isCaptain={isCaptain}
136147
isMember={isMember}
137148
isJoining={isJoining}
149+
isConfirmed={isConfirmed}
138150
handleJoinClick={handleJoinClick}
139151
handleLeaveCrew={handleLeaveCrew}
140152
handleDelete={handleDelete}

src/app/(crew)/crew/detail/[id]/_components/detail-crew-presenter.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface DetailCrewPresenterProps {
1616
isCaptain: boolean;
1717
isMember: boolean;
1818
isJoining: boolean;
19+
isConfirmed: boolean;
1920
handleJoinClick: () => void;
2021
handleLeaveCrew: () => void;
2122
handleDelete: () => void;
@@ -26,6 +27,7 @@ export default function DetailCrewPresenter({
2627
data,
2728
isCaptain,
2829
isMember,
30+
isConfirmed,
2931
handleJoinClick,
3032
handleLeaveCrew,
3133
handleDelete,
@@ -42,14 +44,14 @@ export default function DetailCrewPresenter({
4244
totalCount,
4345
imageUrl,
4446
crewMembers,
45-
confirmed,
4647
} = data;
4748

4849
// captain과 members 분리
4950
const captain = crewMembers.find((member) => member.captain) as CrewDetailMember;
5051
const members = crewMembers.filter((member) => !member.captain);
5152

5253
const getJoinButtonText = () => {
54+
if (isConfirmed) return '모집 완료';
5355
if (isMember) return '참여 완료';
5456
if (isJoining) return '참여 중...';
5557
return '크루 참여하기';
@@ -71,23 +73,23 @@ export default function DetailCrewPresenter({
7173
<Button
7274
className={cn('px-4 py-2', isMember ? 'btn-disabled' : 'btn-filled')}
7375
onClick={handleJoinClick}
74-
disabled={isMember || isJoining}
76+
disabled={isMember || isJoining || isConfirmed}
7577
>
7678
{getJoinButtonText()}
7779
</Button>
7880
</div>
7981

8082
{/* 오른쪽 하단 공유 및 케밥 버튼 */}
81-
<div className="absolute bottom-6 right-6 flex space-x-2">
83+
<div className="absolute bottom-6 right-6 flex items-center space-x-2">
8284
<button type="button" className="bg-transparent" onClick={onShareClick}>
8385
<Image src={ShareIco} alt="공유하기" className="h-6 w-6" />
8486
</button>
8587
{isCaptain || isMember ? (
8688
<Menu trigger="click" position="bottom-end" openDelay={100} closeDelay={400}>
8789
<Menu.Target>
88-
<div className="cursor-pointer">
90+
<button type="button" className="cursor-pointer">
8991
<Image src={KebabIcon} alt="더보기" width={20} height={20} />
90-
</div>
92+
</button>
9193
</Menu.Target>
9294
<Menu.Dropdown>
9395
{isCaptain ? (
@@ -151,19 +153,20 @@ export default function DetailCrewPresenter({
151153
<div className="mb-2 flex items-center justify-between">
152154
<div className="flex items-center">
153155
<Image src={IcoUser} alt="유저 아이콘" width={20} height={20} />
154-
<span className="text-base font-medium text-gray-700">참여인원</span>
156+
<span className="text-base font-medium text-gray-700">크루멤버</span>
155157
<span className="pl-1 text-base font-medium text-blue-500">{participantCount}</span>
156158
<span className="text-base font-medium text-gray-700">/{totalCount}</span>
157159
</div>
158-
{confirmed && (
160+
{isConfirmed && (
159161
<div className="flex items-center text-blue-600">
160-
<Image src={Check} alt="개설 확정" width={24} height={24} />
161-
<span className="text-sm font-medium">개설확정</span>
162+
<Image src={Check} alt="모집 완료 아이콘" width={24} height={24} />
163+
<span className="text-sm font-medium">모집 완료</span>
162164
</div>
163165
)}
164166
</div>
165167
<div className="w-full">
166168
<ProgressBar total={totalCount} current={participantCount} />
169+
<p className="pt-1 text-sm text-gray-500">크루장을 제외한 멤버 목록입니다.</p>
167170
</div>
168171
<div className="mt-4 h-40 space-y-6 overflow-y-auto">
169172
{members.length > 0 ? (

src/app/(crew)/crew/detail/[id]/_components/detail-crew.stories.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export const Default: Story = {
3333
subLocation: '유성구',
3434
participantCount: 8,
3535
totalCount: 10,
36-
confirmed: true,
3736
imageUrl:
3837
'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/4d0c5851-e6e2-4919-897a-b8d4e88a4f72',
3938
totalGatheringCount: 5,
@@ -117,7 +116,6 @@ export const IsCaptain: Story = {
117116
subLocation: '유성구',
118117
participantCount: 1,
119118
totalCount: 10,
120-
confirmed: true,
121119
imageUrl:
122120
'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/4d0c5851-e6e2-4919-897a-b8d4e88a4f72',
123121
totalGatheringCount: 1,
@@ -149,7 +147,6 @@ export const IsMember: Story = {
149147
subLocation: '유성구',
150148
participantCount: 2,
151149
totalCount: 10,
152-
confirmed: true,
153150
imageUrl:
154151
'https://crewcrew.s3.ap-northeast-2.amazonaws.com/crew/4d0c5851-e6e2-4919-897a-b8d4e88a4f72',
155152
totalGatheringCount: 1,

src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal/container.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ export interface GatheringDetailModalContainerProps {
1414
opened: boolean;
1515
close: () => void;
1616
data: GatheringDetailType;
17+
onUpdate?: () => void;
1718
}
1819

19-
// NOTE: 테스트는 로그인 후 토큰이 안담겨서 추후 진행하겠습니다!
20-
2120
export default function GatheringDetailModalContainer({
2221
opened,
2322
close,
2423
data,
24+
onUpdate,
2525
}: GatheringDetailModalContainerProps) {
2626
const showToast = (message: string, type: 'success' | 'error' | 'warning') => {
2727
toast(message, { type });
@@ -32,6 +32,7 @@ export default function GatheringDetailModalContainer({
3232
await JoinGathering(data.crewId, data.id);
3333
showToast('약속에 참여했습니다.', 'success');
3434
close();
35+
onUpdate?.();
3536
} catch (error) {
3637
if (error instanceof ApiError) {
3738
showToast(`참여 중 에러 발생: ${error.message}`, 'error');
@@ -43,6 +44,7 @@ export default function GatheringDetailModalContainer({
4344
try {
4445
await LeaveGathering(data.crewId, data.id);
4546
close();
47+
onUpdate?.();
4648
} catch (error) {
4749
if (error instanceof ApiError) {
4850
showToast(`참여 취소 중 에러 발생: ${error.message}`, 'error');
@@ -55,6 +57,7 @@ export default function GatheringDetailModalContainer({
5557
await CancelGathering(data.crewId, data.id);
5658
showToast('약속을 삭제했습니다.', 'success');
5759
close();
60+
onUpdate?.();
5861
} catch (error) {
5962
if (error instanceof ApiError) {
6063
showToast(`약속 삭제 중 에러 발생: ${error.message}`, 'error');

src/app/(crew)/crew/detail/[id]/_components/gathering-detail-modal/gathering-detail-modal.stories.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,19 @@ const Template: StoryFn<GatheringDetailModalContainerProps> = function Gathering
4343
setIsOpened(opened);
4444
}, [opened]);
4545

46+
const handleUpdate = () => {
47+
action('Update action performed')();
48+
};
49+
4650
return (
4751
<>
4852
<Button onClick={handleOpen}>Open Modal</Button>
49-
<GatheringDetailModalContainer opened={isOpened} close={handleClose} data={data} />
53+
<GatheringDetailModalContainer
54+
opened={isOpened}
55+
close={handleClose}
56+
data={data}
57+
onUpdate={handleUpdate}
58+
/>
5059
</>
5160
);
5261
};

src/app/(crew)/crew/detail/[id]/_components/gathering-list-section.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState } from 'react';
44
import { toast } from 'react-toastify';
55
import { useRouter } from 'next/navigation';
6+
import { Loader } from '@mantine/core';
67
import { addLike, removeLike } from '@/src/_apis/liked/liked-apis';
78
import { useGetGatheringListQuery } from '@/src/_queries/crew/gathering-list-queries';
89
import { ApiError } from '@/src/utils/api';
@@ -14,7 +15,7 @@ interface GatheringListSectionProps {
1415
}
1516

1617
export default function GatheringListSection({ id }: GatheringListSectionProps) {
17-
const { data: gatheringList, isLoading, error } = useGetGatheringListQuery(id);
18+
const { data: gatheringList, isLoading, error, refetch } = useGetGatheringListQuery(id);
1819
const [showLoginModal, setShowLoginModal] = useState(false);
1920
const router = useRouter();
2021

@@ -43,11 +44,15 @@ export default function GatheringListSection({ id }: GatheringListSectionProps)
4344
router.push(`/login?redirect=${encodeURIComponent(currentPath)}`);
4445
};
4546

47+
const handleModalAction = () => {
48+
refetch();
49+
};
50+
4651
// TODO: 추후 에러, 로딩 수정
4752
if (isLoading)
4853
return (
4954
<div className="flex items-center justify-center">
50-
<p>로딩 중...</p>
55+
<Loader />
5156
</div>
5257
);
5358

@@ -73,6 +78,7 @@ export default function GatheringListSection({ id }: GatheringListSectionProps)
7378
onLike={handleLike}
7479
onUnlike={handleUnlike}
7580
onShowLoginModal={() => setShowLoginModal(true)}
81+
onModalAction={handleModalAction}
7682
/>
7783
{showLoginModal && (
7884
<ConfirmModal

src/app/(crew)/crew/detail/[id]/page.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ export default async function CrewDetailPage({ params }: CrewDetailPageProps) {
2323
{/* Gathering Section */}
2424
<section className="w-full space-y-6">
2525
<article className="space-y-6">
26-
<div className="flex items-center justify-between px-3 md:px-7 lg:px-11">
27-
<h2 className="text-2xl font-semibold">크루 약속</h2>
28-
<CreateGathering crewId={Number(params.id)} />
29-
</div>
26+
<CreateGathering crewId={Number(params.id)} />
3027
<div className="flex w-full overflow-hidden px-3 md:px-7 lg:px-0">
3128
<div className="relative -mx-3 w-[calc(100%+1.5rem)] px-3 md:-mx-7 md:w-[calc(100%+3.5rem)] md:px-7 lg:-mx-6 lg:w-[calc(100%+3rem)] lg:px-0">
3229
<GatheringListSection id={id} />

0 commit comments

Comments
 (0)