Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
53 changes: 42 additions & 11 deletions apps/what-today/src/pages/mypage/reservations-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
NoResult,
RadioGroup,
ReservationCard,
SpinIcon,
StarRating,
} from '@what-today/design-system';
import { WarningLogo } from '@what-today/design-system';
Expand All @@ -19,12 +20,25 @@ import { cancelMyReservation, createReview, fetchMyReservations } from '@/apis/m
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import type { MyReservationsResponse, Reservation, ReservationStatus } from '@/schemas/myReservations';

// 필터링 가능한 상태 타입 (전체 상태 + 빈 문자열)
type FilterStatus = ReservationStatus | '';

// 예약 상태별 NoResult 메시지 상수 (컴포넌트 외부에 정의하여 최적화)
const NO_RESULT_MESSAGES: Record<FilterStatus, string> = {
'': '예약한 체험이',
pending: '대기 중인 예약이',
confirmed: '승인된 예약이',
declined: '거절된 예약이',
canceled: '취소된 예약이',
completed: '완료된 체험이',
};

export default function ReservationsListPage() {
const navigate = useNavigate();
const { toast } = useToast();
const queryClient = useQueryClient();

const [selectedStatus, setSelectedStatus] = useState<string>('');
const [selectedStatus, setSelectedStatus] = useState<FilterStatus>('');

const [cancelTarget, setCancelTarget] = useState<Reservation | null>(null);
const isDeleteOpen = cancelTarget !== null;
Expand Down Expand Up @@ -54,6 +68,15 @@ export default function ReservationsListPage() {

const reservations = data?.pages.flatMap((page) => page.reservations) ?? [];

const noResultMessage = NO_RESULT_MESSAGES[selectedStatus];

// 리뷰 모달 닫기 핸들러
const handleCloseReviewModal = () => {
setReviewTarget(null);
setReviewContent('');
setStarRating(0);
};

const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const observerRef = useIntersectionObserver(
fetchNextPage,
Expand Down Expand Up @@ -93,14 +116,12 @@ export default function ReservationsListPage() {
type: 'success',
});
setReviewTarget(null);
setReviewContent('');
setStarRating(0);
queryClient.invalidateQueries({ queryKey: ['reservations'] });
},
onError: () => {
onError: (error) => {
toast({
title: '후기 작성 실패',
description: '후기 작성 중 문제가 발생했습니다.',
description: error instanceof Error ? error.message : '후기 작성 중 문제가 발생했습니다.',
type: 'error',
});
},
Expand Down Expand Up @@ -128,6 +149,7 @@ export default function ReservationsListPage() {
{group.map((res) => {
const showCancelButton = res.status === 'pending';
const showReviewButton = res.status === 'completed' && !res.reviewSubmitted;
const shoowReviewCompletedButton = res.status === 'completed' && res.reviewSubmitted;

return (
<li key={res.id} className='mb-24'>
Expand All @@ -142,7 +164,7 @@ export default function ReservationsListPage() {
onClick={() => navigate(`/activities/${res.activity.id}`)}
/>

{(showCancelButton || showReviewButton) && (
{(showCancelButton || showReviewButton || shoowReviewCompletedButton) && (
<div className='mt-8 mb-24'>
{showCancelButton && (
<Button
Expand All @@ -164,6 +186,11 @@ export default function ReservationsListPage() {
후기 작성
</Button>
)}
{shoowReviewCompletedButton && (
<Button disabled className='caption-text w-full' size='md' variant='fill'>
후기 작성 완료
</Button>
)}
</div>
)}
</li>
Expand All @@ -176,13 +203,17 @@ export default function ReservationsListPage() {

let content;
if (isLoading) {
content = <div className='flex justify-center p-40 text-gray-400'>로딩 중...</div>;
content = (
<div className='flex items-center justify-center p-40'>
<SpinIcon className='size-200' color='var(--color-gray-100)' />
</div>
);
} else if (reservations.length > 0) {
content = <div className='space-y-10'>{renderGroupedReservations(reservations)}</div>;
} else {
content = (
<div className='flex justify-center p-40'>
<NoResult dataName='예약한 체험이' />
<NoResult dataName={noResultMessage} />
</div>
);
}
Expand All @@ -203,7 +234,7 @@ export default function ReservationsListPage() {
<RadioGroup
radioGroupClassName='flex flex-nowrap gap-6 overflow-x-auto no-scrollbar'
selectedValue={selectedStatus}
onSelect={(value) => setSelectedStatus(String(value))}
onSelect={(value) => setSelectedStatus(value as FilterStatus)}
>
<RadioGroup.Radio value='pending'>예약 대기</RadioGroup.Radio>
<RadioGroup.Radio value='confirmed'>예약 승인</RadioGroup.Radio>
Expand Down Expand Up @@ -237,12 +268,12 @@ export default function ReservationsListPage() {
</Modal.Content>
</Modal.Root>

<Modal.Root open={isReviewOpen} onClose={() => setReviewTarget(null)}>
<Modal.Root open={isReviewOpen} onClose={handleCloseReviewModal}>
{reviewTarget && (
<Modal.Content className='flex max-w-385 flex-col items-center gap-6 text-center'>
<Modal.CloseButton />
<h2 className='section-text mt-22 font-bold'>{reviewTarget.activity.title}</h2>
<p className='caption-text text-gray-400'>
<p className='caption-text mb-6 text-gray-400'>
{reviewTarget.date}/ {reviewTarget.startTime} ~ {reviewTarget.endTime} ({reviewTarget.headCount}명)
</p>

Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/src/components/StarRating.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function StarRating({ value, onChange, max = 5, className }: Star
onClick={() => onChange(starValue)}
onMouseEnter={() => setHoverValue(starValue)}
>
<StarIcon className={twMerge('size-42', !isLast && 'mr-12')} filled={isFilled} hover={isHover} />
<StarIcon className={twMerge('size-35', !isLast && 'mr-5')} filled={isFilled} hover={isHover} />
</Button>
);
})}
Expand Down