Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e7a2237
chore: Dropdown 컴포넌트 위치 이동
Squarecat-meow Sep 5, 2025
7186caa
feat(myReservationStatus): 예약 현황 페이지 Dropdown까지 작성
Squarecat-meow Sep 5, 2025
5eb23fc
fix(myExperiences): 내 체험 관리 queryKey queries파일에서 가져오게
Squarecat-meow Sep 5, 2025
c4ecd8a
chre: 주석 제거
Squarecat-meow Sep 6, 2025
7c543ad
fix(API): 내 체험 달력 가져오는 API에 체험 ID 넣을 수 있게
Squarecat-meow Sep 6, 2025
2665dd7
fix(ReservationCalendar): 예약 현황 달력 예외처리
Squarecat-meow Sep 6, 2025
180bc45
feat(myReservationStatus): 예약 현황 페이지에 달력 추가
Squarecat-meow Sep 6, 2025
586f8aa
fix(MyExperienceCard): SubImage배열에서 BannerImage로 변경
Squarecat-meow Sep 8, 2025
6e54b37
Merge branch 'hong0121:dev' into saeron-jung/primitive
Squarecat-meow Sep 9, 2025
3fea56f
refactor(Dropdown): title
Squarecat-meow Sep 9, 2025
948d411
feat(ReservationCalendar): 예약 현황 캘린더 작성
Squarecat-meow Sep 9, 2025
82d1cf0
fix(myExperiences): accessToken 불필요하게
Squarecat-meow Sep 9, 2025
58708c1
Merge branch 'hong0121:dev' into saeron-jung/primitive
Squarecat-meow Sep 10, 2025
a1f3791
Merge branch 'hong0121:dev' into saeron-jung/primitive
Squarecat-meow Sep 11, 2025
0554ea6
fix(myInfo): div에 id를 붙여서 스크롤 방지
Squarecat-meow Sep 11, 2025
c45e847
feat(Responsive): 예약 현황 반응형 UI 구현
Squarecat-meow Sep 11, 2025
891a27d
refactor(main/api): 따옴표, any 타입 ESLint disable
Squarecat-meow Sep 11, 2025
34e625b
feat(myActivities): 예약 현황 fetch 모듈 작성
Squarecat-meow Sep 11, 2025
ff2328e
Merge branch 'dev' into saeron-jung/primitive
Squarecat-meow Sep 12, 2025
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
2 changes: 1 addition & 1 deletion src/app/(global)/(mypage)/myCreateExperiences/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client';

import AvailableTimeSlots from '@/src/components/pages/myCreateExperiences/AvailableTimeSlots';
import Dropdown from '@/src/components/pages/myCreateExperiences/Dropdown';
import UploadBannerImage from '@/src/components/pages/myCreateExperiences/UploadBannerImage';
import Button from '@/src/components/primitives/Button';
import Dropdown from '@/src/components/primitives/Dropdown';
import FormInput from '@/src/components/primitives/input/FormInput';
import { useReservationStore } from '@/src/store/ReservationStore';
import { useTimeSlotStore } from '@/src/store/TimeSlotStore';
Expand Down
11 changes: 6 additions & 5 deletions src/app/(global)/(mypage)/myInfo/_components/myExperiences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
import MyExperienceCard from '@/src/components/pages/myExperiences/MyExperienceCard';
import Button from '@/src/components/primitives/Button';
import LoadingSpinner from '@/src/components/primitives/LoadingSpinner';
import { getMyExperiences } from '@/src/services/pages/myExperiences/api';
import { useQuery } from '@tanstack/react-query';
import Image from 'next/image';
import BackIcon from '@/public/images/icons/BackIcon.svg';
import { useContext } from 'react';
import { TabContext } from '../pageContext';
import { queries } from '@/src/services/primitives/queries';
import { useTokenStore } from '@/src/store/useTokenStore';

export default function MyExperiencesPage() {
const { data, isPending } = useQuery({
queryKey: ['myExperiences'],
queryFn: getMyExperiences,
});
const { setIsTabOpen } = useContext(TabContext);
const { accessToken } = useTokenStore();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const accessToken = useTokenStore(state => state.accessToken) 으로 수정해주셔야 할거 같아요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 그러네요!

const { data, isPending } = useQuery(
queries.myExperiencesOptions(accessToken)
);

return (
<section className='flex flex-col items-center gap-8'>
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/(global)/(mypage)/myInfo/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { TabContext } from './pageContext';
import LeftSidebar from '@/src/components/pages/sidebar/LeftSidebar';
import MyExperiencesPage from './_components/myExperiences';
import MyInfoPage from './_components/myInfo';
import MyReservationStatusPage from './_components/myReservationStatus';
import MyReservationStatusPage from '@/src/components/pages/myReservationStatus/myReservationStatus';
import MyReservation from '@/src/components/pages/myReservation/MyReservation';

export interface ISidebarButtons {
Expand Down
3 changes: 2 additions & 1 deletion src/components/pages/myExperiences/MyExperienceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import StarIcon from '@/public/images/icons/StarFilled.svg';
import ExperienceButton from './ExperienceButton';

export default function MyExperienceCard({ data }: { data: IActivity }) {
console.log(data);
// TODO: 카드 클릭하면 체험 관리 페이지로 가게
return (
<div className='w-full p-6 flex justify-between rounded-3xl shadow'>
Expand Down Expand Up @@ -32,7 +33,7 @@ export default function MyExperienceCard({ data }: { data: IActivity }) {
</div>
</div>
<img
src={data.subImages[0].imageUrl}
src={data.bannerImageUrl}
alt='행사 이미지 URL'
className='h-[82px] lg:h-[142px] aspect-square rounded-3xl object-cover'
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useReservationStore } from '@/src/store/ReservationStore';
export default function ReservationCalendar({
schedule,
}: {
schedule: IReservedSchedule[];
schedule: IReservedSchedule[] | null;
}) {
const { displayController } = useReservationStore();
const daysArray = getDaysArray(new Date());
Expand Down
103 changes: 103 additions & 0 deletions src/components/pages/myReservationStatus/myReservationStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client';

import { useEffect, useState } from 'react';
import Dropdown from '../../primitives/Dropdown';
import LoadingSpinner from '../../primitives/LoadingSpinner';
import Image from 'next/image';
import BackBtn from '../../primitives/mypage/BackBtn';
import { Activity, IMyReservations } from '@/src/types/activityType';
import ReservationCalendar from './ReservationCalendar';
import {
getMyExperiences,
getMyReservationStatus,
} from '@/src/services/pages/myExperiences/api';
import { useReservationStore } from '@/src/store/ReservationStore';
import { format } from 'date-fns';
import { IReservedSchedule } from '@/src/types/scheduleType';

export default function MyReservationStatusPage() {
const [activities, setActivities] = useState<IMyReservations | null>();

useEffect(() => {
const fn = async () => {
const activitiesList = await getMyExperiences();
if (!activitiesList || activitiesList.totalCount === 0)
setActivities(null);
setActivities(activitiesList);
};
fn();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 보기엔 이 코드에 리액트쿼리를 사용해야할 것 같은 부분인데, 사용하지 않은 이유가 있으신가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네 그러네요ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ이것도 팩토리에 넣어두겠습니다!

}, []);

console.log(activities);

return (
<section className='flex flex-col items-center gap-8'>
<div className='w-full flex flex-col gap-2 md:justify-between'>
<div className='flex gap-4'>
<BackBtn />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

백버튼 위치를 다 합치고 나서 한번에 좀 정리를 해야할거 같네요 😅

<h1 className='text-18 font-bold'>예약 현황</h1>
</div>
<p className='font-14 text-gray-500'>
내 체험에 예약된 내역들을 한 눈에 확인할 수 있습니다.
</p>
</div>
{activities !== undefined ? (
<>
{activities !== null && activities.totalCount !== 0 ? (
<DropdownAndCalendar data={activities.activities} />
) : (
<div className='space-y-8'>
<Image
src={'/images/Not_Found_Earth.png'}
alt='찾을 수 없습니다'
width={122}
height={122}
className='mx-auto'
/>
<span className='text-18 text-gray-600'>
아직 등록한 체험이 없어요
</span>
</div>
)}
</>
) : (
<LoadingSpinner />
)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

분기처리가 너무 세세해서 한눈에 안들어오는거 같아요.
jsx 문법이 시작하기 전에 if(activities === undefined) return <LoadingSpinner /> 해주는게 좀 더 깔끔할거 같아요.

</section>
);
}

function DropdownAndCalendar({ data }: { data: Activity[] }) {
const [selectedExperience, setSelectedExperience] = useState<string | null>(
null
);
const [reservedSchedule, setReservedSchedule] = useState<
IReservedSchedule[] | null
>(null);

const calendarDisplayDate = useReservationStore(
(state) => state.displayController.dateToDisplay
);

Promise.all(
data.map((el) => {
return getMyReservationStatus(
el.id,
format(calendarDisplayDate, 'yyyy'),
format(calendarDisplayDate, 'MM')
);
})
).then((res) => setReservedSchedule(res));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api 요청을 하나만 하는데, Promise.all이 필요할까 싶습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data로 받아온 모든 체험 일정들을 map으로 순회하면서 API 요청을 동기로 받아오는 부분이에요!


return (
<div>
<Dropdown
label=''
items={data.map((el) => el.title)}
value={selectedExperience}
onChange={(e) => setSelectedExperience(e)}
/>
<ReservationCalendar schedule={reservedSchedule} />
</div>
);
}
20 changes: 20 additions & 0 deletions src/services/pages/myExperiences/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IMyReservations } from '@/src/types/activityType';
import { apiClient } from '../../primitives/apiClient';
import { IReservedSchedule } from '@/src/types/scheduleType';

// 내 체험 삭제
export async function deleteActivityById(activityId: number) {
Expand All @@ -24,3 +25,22 @@ export async function getMyExperiences(): Promise<IMyReservations> {
throw err;
}
}

// 내 체험 별로 예약 달력 가져오기
export async function getMyReservationStatus(
activityId: number,
year: string,
month: string
): Promise<IReservedSchedule> {
try {
const res = await apiClient.get(
`/my-activities/${activityId}/reservation-dashboard`,
{ params: { year, month } }
);
return res.data;
} catch (err) {
if (err instanceof Error)
console.error('예약 목록을 가져오는데 실패했습니다:', err.message);
throw err;
}
}
8 changes: 8 additions & 0 deletions src/services/primitives/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getMyReservationList } from '@/src/services/pages/myReservation/api';
import getUserInfo from '@/src/services/primitives/getUserInfo';
import { ReservationStatus } from '@/src/types/myReservationType';
import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';
import { getMyExperiences } from '../pages/myExperiences/api';

export const queries = {
user: () => ['user'],
Expand All @@ -23,4 +24,11 @@ export const queries = {
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.cursorId,
}),
myExperiences: () => ['myExperiences'],
myExperiencesOptions: (accessToken: string | null | undefined) =>
queryOptions({
queryKey: [...queries.myExperiences()],
queryFn: () => getMyExperiences(),
enabled: !!accessToken,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthProvider에서 유저가 로그인되지 않은 상황이라면, 리다이렉트로 마이페이지에 접속하지 못하도록 처리가 되어 있어서, accessToken으로 enabled 처리를 해줄 필요가 있나 싶네요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오... 아예 접근 자체가 안되면 accessToken은 빼도 될 것 같습니다!

}),
};
29 changes: 19 additions & 10 deletions src/utils/mergeTwoDateArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,30 @@ import {
} from '../types/scheduleType';

export function mergeScheduleWithDays(
schedule: IReservedSchedule[],
schedule: IReservedSchedule[] | null,
days: Date[]
) {
const scheduleMap = new Map<string, IScheduleCount>();

schedule.forEach((item) => {
scheduleMap.set(item.date, item.reservations);
});
if (!schedule) {
return days.map((day) => {
return {
schedule: null,
date: day,
};
});
} else {
schedule.forEach((item) => {
scheduleMap.set(item.date, item.reservations);
});

return days.map((day) => {
return {
schedule: scheduleMap.get(format(day, 'yyyy-MM-dd')) ?? null,
date: day,
};
});
return days.map((day) => {
return {
schedule: scheduleMap.get(format(day, 'yyyy-MM-dd')) ?? null,
date: day,
};
});
}
}

export function availableDateWithDaysArray(
Expand Down