diff --git a/mock.json b/mock.json index 02b8fa56..57d6cc5b 100644 --- a/mock.json +++ b/mock.json @@ -335,5 +335,40 @@ "isConfirmed": true, "gatheringCount": 5 } + ], + "gatheringDetail": [ + { + "id": 1, + "title": "신나는 운동...즐거운..코딩..", + "introduce": "공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~", + "dateTime": "2024-10-29T00:32:12.306Z", + "location": "서울시 강남구 역삼동 오피스타워 3층", + "currentCount": 3, + "totalCount": 10, + "imageUrl": "https://www.dabur.com/Blogs/Doshas/Importance%20and%20Benefits%20of%20Yoga%201020x450.jpg", + "isLiked": false, + "isGatherCaptain": false, + "isParticipant": true, + "participants": [ + { + "id": 1, + "profileImageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUMrcQB5OJ-ETzPc6wHnjxjC-36__MGw3JcA&s", + "nickname": "럽윈즈올", + "email": "youl@email.com" + }, + { + "id": 2, + "profileImageUrl": "https://imgcdn.stablediffusionweb.com/2024/5/13/c0541236-e690-4dff-a27e-30a0355e5ea0.jpg", + "nickname": "모닝러너", + "email": "youl@email.com" + }, + { + "id": 3, + "profileImageUrl": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUMrcQB5OJ-ETzPc6wHnjxjC-36__MGw3JcA&s", + "nickname": "동글동글이", + "email": "youl@email.com" + } + ] + } ] } diff --git a/src/_apis/gathering/gathering-apis.tsx b/src/_apis/gathering/gathering-apis.tsx new file mode 100644 index 00000000..48592dc4 --- /dev/null +++ b/src/_apis/gathering/gathering-apis.tsx @@ -0,0 +1,11 @@ +import { fetchApi } from '@/src/utils/api'; +import { GatheringDetailType } from '@/src/types/gathering-data'; + +export function getGathering(): Promise { + return fetchApi('/gatheringDetail/1', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then((response) => response); // TODO: data 추출 +} diff --git a/src/_queries/gathering/gathering-queries.tsx b/src/_queries/gathering/gathering-queries.tsx new file mode 100644 index 00000000..5f55a20a --- /dev/null +++ b/src/_queries/gathering/gathering-queries.tsx @@ -0,0 +1,11 @@ +import { getGathering } from '@/src/_apis/gathering/gathering-apis'; +import { transformKeysToCamel } from '@/src/utils/transform-keys'; +import { GatheringDetailType } from '@/src/types/gathering-data'; + +export function useGetGatheringQuery() { + return { + queryKey: ['gatheringDetail'], + queryFn: getGathering, + select: (data: GatheringDetailType) => transformKeysToCamel(data), + }; +} diff --git a/src/app/(crew)/layout.tsx b/src/app/(crew)/layout.tsx index 372ffe84..dd0adbf3 100644 --- a/src/app/(crew)/layout.tsx +++ b/src/app/(crew)/layout.tsx @@ -11,7 +11,7 @@ export default function RootLayout({ <>
-
+
{children}
diff --git a/src/app/(crew)/my-gathering/_component/gathering-list-with-date.tsx b/src/app/(crew)/my-gathering/_component/gathering-list-with-date.tsx new file mode 100644 index 00000000..e3ef0897 --- /dev/null +++ b/src/app/(crew)/my-gathering/_component/gathering-list-with-date.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { useMemo } from 'react'; +import { formatDate } from '@/src/utils/format-date'; +import ScheduledGatheringCard from '@/src/components/common/gathering-card/scheduled-gathering-card/container'; +import { GatheringCardProps } from '@/src/types/gathering-data'; + +interface GatheringListWithDateProps { + gatheringList: GatheringCardProps[]; +} + +export default function GatheringListWithDate({ gatheringList }: GatheringListWithDateProps) { + const gatheringListWithDateInfo = useMemo(() => { + return gatheringList.map((gathering, index) => { + const isNewDate = + index === 0 || + formatDate(gathering.dateTime).date !== formatDate(gatheringList[index - 1].dateTime).date; + return { ...gathering, isNewDate }; + }); + }, [gatheringList]); + + return ( +
+ {gatheringListWithDateInfo.map((gathering) => ( +
+
+ {gathering.isNewDate && ( +
+
+
+
{formatDate(gathering.dateTime).date}
+
월요일
+
+
+ )} +
+
+ +
+
+ ))} +
+ ); +} diff --git a/src/app/(crew)/my-gathering/creation/page.tsx b/src/app/(crew)/my-gathering/creation/page.tsx new file mode 100644 index 00000000..7f4dbc98 --- /dev/null +++ b/src/app/(crew)/my-gathering/creation/page.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import GatheringListWithDate from '@/src/app/(crew)/my-gathering/_component/gathering-list-with-date'; +import PopOverCalendar from '@/src/components/common/input/pop-over-calendar'; +import { GatheringCardProps } from '@/src/types/gathering-data'; + +export default function CreationPage() { + const [selectedDate, setSelectedDate] = useState(new Date()); + + useEffect(() => {}, [selectedDate]); + const creationGatheringList: GatheringCardProps[] = [ + { + id: 1, + crewTitle: 'Power Pole', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-10-30T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + { + id: 2, + crewTitle: '풀 엔 그레이스 스튜디오', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-10-30T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + { + id: 3, + crewTitle: '풀 엔 그레이스 스튜디오', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-11-11T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + { + id: 4, + crewTitle: '풀 엔 그레이스 스튜디오', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-11-21T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + ]; + + return ( +
+
+ setSelectedDate(d)} /> +
+ +
+ ); +} +function setState(arg0: Date) { + throw new Error('Function not implemented.'); +} diff --git a/src/app/(crew)/my-gathering/favorite/page.tsx b/src/app/(crew)/my-gathering/favorite/page.tsx new file mode 100644 index 00000000..f73dbb0a --- /dev/null +++ b/src/app/(crew)/my-gathering/favorite/page.tsx @@ -0,0 +1,10 @@ +import GatheringList from '@/src/components/gathering-list/gathering-list'; +import { gatheringData } from '@/src/mock/gathering-data'; + +export default function FavoritePage() { + return ( +
+ +
+ ); +} diff --git a/src/app/(crew)/my-gathering/layout.tsx b/src/app/(crew)/my-gathering/layout.tsx new file mode 100644 index 00000000..3b0f6747 --- /dev/null +++ b/src/app/(crew)/my-gathering/layout.tsx @@ -0,0 +1,48 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { Button } from '@mantine/core'; + +const buttonData = [ + { id: 1, label: '참여한 약속', route: '/my-gathering/participation' }, + { id: 2, label: '만든 약속', route: '/my-gathering/creation' }, + { id: 3, label: '찜한 약속', route: '/my-gathering/favorite' }, +]; + +const getSelectedButtonIndex = (currentPath: string) => { + return buttonData.findIndex(({ route }) => currentPath.endsWith(route.split('/').pop()!)) + 1; +}; + +export default function MyGatheringLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const currentPath = usePathname(); + + const [selectedButton, setSelectedButton] = useState(getSelectedButtonIndex(currentPath)); + + useEffect(() => { + const newIdx = getSelectedButtonIndex(currentPath); + setSelectedButton(newIdx); + }, [currentPath]); + + return ( +
+
+ {buttonData.map(({ id, label, route }) => ( + + + + ))} +
+ {children} +
+ ); +} diff --git a/src/app/(crew)/my-gathering/page.tsx b/src/app/(crew)/my-gathering/page.tsx index ff1ce9f6..a446078c 100644 --- a/src/app/(crew)/my-gathering/page.tsx +++ b/src/app/(crew)/my-gathering/page.tsx @@ -1,16 +1,6 @@ -'use client'; - -import { useState } from 'react'; -import CalendarFilter from '@/src/components/common/input/calendar-filter'; - -const toDoDates = [new Date('2024-10-12'), new Date('2024-10-15')]; +import { redirect } from 'next/navigation'; export default function MyGatheringPage() { - const [date, setDate] = useState(new Date()); - - return ( -
- -
- ); + redirect('/my-gathering/participation'); + return null; } diff --git a/src/app/(crew)/my-gathering/participation/page.tsx b/src/app/(crew)/my-gathering/participation/page.tsx new file mode 100644 index 00000000..638d6dcd --- /dev/null +++ b/src/app/(crew)/my-gathering/participation/page.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import GatheringListWithDate from '@/src/app/(crew)/my-gathering/_component/gathering-list-with-date'; +import PopOverCalendar from '@/src/components/common/input/pop-over-calendar'; +import { GatheringCardProps } from '@/src/types/gathering-data'; + +export default function ParticipationPage() { + const [selectedDate, setSelectedDate] = useState(new Date()); + + useEffect(() => {}, [selectedDate]); + const creationGatheringList: GatheringCardProps[] = [ + { + id: 1, + crewTitle: '풀 엔 그레이스 스튜디오', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-10-30T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + { + id: 2, + crewTitle: '풀 엔 그레이스 스튜디오', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-10-30T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + { + id: 3, + crewTitle: '풀 엔 그레이스 스튜디오', + crewMainLocation: '서울시', + crewSubLocation: '강남구 역삼동', + title: '가나다라마가나다라마가나다라마가', + dateTime: '2024-10-31T00:30', + currentCount: 8, + totalCount: 12, + imageUrl: + 'https://images.unsplash.com/photo-1601758260892-a62c486ace97?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D', + isLiked: true, + }, + ]; + + return ( +
+
+ setSelectedDate(d)} /> +
+ +
+ ); +} +function setState(arg0: Date) { + throw new Error('Function not implemented.'); +} diff --git a/src/components/common/gathering-card/scheduled-gathering-card/container.tsx b/src/components/common/gathering-card/scheduled-gathering-card/container.tsx index 4a123271..27e7c40c 100644 --- a/src/components/common/gathering-card/scheduled-gathering-card/container.tsx +++ b/src/components/common/gathering-card/scheduled-gathering-card/container.tsx @@ -1,4 +1,5 @@ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; +import GatheringDetailModalContainer from '@/src/app/(crew)/crew/_components/gathering-detail-modal/container'; import ScheduledGatheringCardPresenter from './presenter'; interface ScheduledGatheringCardContainerProps { @@ -19,8 +20,50 @@ interface ScheduledGatheringCardContainerProps { export default function ScheduledGatheringCardContainer({ data, }: ScheduledGatheringCardContainerProps) { + const [isOpened, setIsOpened] = useState(false); + // TODO: modalData 연결 + // const { data: modalData } = useQuery(useGetGatheringQuery()); + const dummyModalData = { + id: 1, + title: '신나는 운동...즐거운..코딩..', + introduce: '공지사항입니다. 다들 이번 약속 잊지 않으셨죠? 꼭 참여 부탁드립니다~', + dateTime: '2024-10-29T00:32:12.306Z', + location: '서울시 강남구 역삼동 오피스타워 3층', + currentCount: 3, + totalCount: 10, + imageUrl: + 'https://www.dabur.com/Blogs/Doshas/Importance%20and%20Benefits%20of%20Yoga%201020x450.jpg', + isLiked: false, + isGatherCaptain: false, + isParticipant: false, + participants: [ + { + id: 1, + profileImageUrl: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUMrcQB5OJ-ETzPc6wHnjxjC-36__MGw3JcA&s', + nickname: '럽윈즈올', + email: 'youl@email.com', + }, + { + id: 2, + profileImageUrl: + 'https://imgcdn.stablediffusionweb.com/2024/5/13/c0541236-e690-4dff-a27e-30a0355e5ea0.jpg', + nickname: '모닝러너', + email: 'youl@email.com', + }, + { + id: 3, + profileImageUrl: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUMrcQB5OJ-ETzPc6wHnjxjC-36__MGw3JcA&s', + nickname: '동글동글이', + email: 'youl@email.com', + }, + ], + }; + const handleCardClick = useCallback(() => { - alert('카드 클릭됨'); // TODO: 모달 열기 로직 구현 + setIsOpened(true); + // TODO: retry or refetch사용해서 useQuery부분 안으로 넣기 }, []); const handleLikeToggle = useCallback(() => { @@ -28,10 +71,21 @@ export default function ScheduledGatheringCardContainer({ }, []); return ( - +
+ + {dummyModalData && ( + { + setIsOpened(false); + }} + data={dummyModalData} + /> + )} +
); } diff --git a/src/components/common/gathering-card/scheduled-gathering-card/presenter.tsx b/src/components/common/gathering-card/scheduled-gathering-card/presenter.tsx index 5e20cbb3..33a372ad 100644 --- a/src/components/common/gathering-card/scheduled-gathering-card/presenter.tsx +++ b/src/components/common/gathering-card/scheduled-gathering-card/presenter.tsx @@ -55,17 +55,17 @@ export default function ScheduledGatheringCardPresenter({ onClick={onClick} role="button" tabIndex={0} - className="relative flex h-44 cursor-pointer items-center space-x-4 rounded-xl bg-white p-4 shadow-xs md:space-x-6 md:p-6 lg:space-x-6 lg:p-6" + className="relative flex h-44 cursor-pointer items-center space-x-4 rounded-xl bg-white p-6 shadow-xs md:space-x-6 lg:space-x-6" > {/* Image Section */} -
+
{title}
{/* Content Section */} -
+
-

+

{title}

diff --git a/src/components/common/input/pop-over-calendar/index.tsx b/src/components/common/input/pop-over-calendar/index.tsx index 0a429d76..5f76e8c7 100644 --- a/src/components/common/input/pop-over-calendar/index.tsx +++ b/src/components/common/input/pop-over-calendar/index.tsx @@ -41,7 +41,7 @@ export default function PopOverCalendar({ value, onChange }: PopOverProps) { onBlur={() => setInputTheme('white')} className="flex h-11 items-center justify-between rounded-xl border-0 bg-white px-3 py-2.5 text-base font-medium text-gray-800 hover:bg-white hover:text-gray-800 focus:bg-black focus:text-white" > - 날짜 전체 + 날짜 선택 diff --git a/src/components/gathering-list/gathering-list.tsx b/src/components/gathering-list/gathering-list.tsx index de3daefe..3f6375b5 100644 --- a/src/components/gathering-list/gathering-list.tsx +++ b/src/components/gathering-list/gathering-list.tsx @@ -52,7 +52,7 @@ export default function GatheringList({ gatheringData }: GatheringListProps) { return (
-
+
{currentPageData.map((card, id) => ( ))} diff --git a/src/styles/globals.css b/src/styles/globals.css index 0b2ebf03..806c6031 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -34,3 +34,10 @@ line-height: 24px; } } +@layer utilities { + .corner-dot { + @apply absolute right-[-7px] top-[-6px] rounded-full bg-gray-300; + width: 12px; + height: 12px; + } +} diff --git a/src/types/gathering-data.d.ts b/src/types/gathering-data.d.ts index 8589da2e..346aeffb 100644 --- a/src/types/gathering-data.d.ts +++ b/src/types/gathering-data.d.ts @@ -33,3 +33,16 @@ export interface CreateGatheringRequestType { totalCount: number; imageUrl: File | null; // NOTE : 임시로 File로 설정 } + +export interface GatheringCardProps { + id: number; + crewTitle: string; + crewMainLocation: string; + crewSubLocation: string; + title: string; + dateTime: string; + currentCount: number; + totalCount: number; + imageUrl: string; + isLiked: boolean; +}