Skip to content
23 changes: 23 additions & 0 deletions public/assets/svg/empty-document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

const EmptyDocumentIcon = ({ size = 24, ...props }) => (
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

TypeScript 타입 정의가 필요합니다.

props에 대한 TypeScript 타입이 정의되지 않았습니다. 타입 안전성을 위해 인터페이스를 추가해주세요.

+interface EmptyDocumentIconProps {
+  size?: number;
+  [key: string]: any;
+}

-const EmptyDocumentIcon = ({ size = 24, ...props }) => (
+const EmptyDocumentIcon = ({ size = 24, ...props }: EmptyDocumentIconProps) => (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const EmptyDocumentIcon = ({ size = 24, ...props }) => (
interface EmptyDocumentIconProps {
size?: number;
[key: string]: any;
}
const EmptyDocumentIcon = ({ size = 24, ...props }: EmptyDocumentIconProps) => (
🤖 Prompt for AI Agents
In public/assets/svg/empty-document.tsx at line 3, the component
EmptyDocumentIcon lacks TypeScript type definitions for its props. Define an
interface specifying the expected prop types, including the optional size as a
number and any other props, then apply this interface to the component's props
parameter to ensure type safety.

<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 131 178'
{...props}
>
Comment on lines +4 to +11
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

접근성을 위한 대체 텍스트가 필요합니다.

스크린 리더 사용자를 위해 SVG에 대체 텍스트를 제공해야 합니다. title 요소나 aria-label 속성을 추가해주세요.

  <svg
    xmlns='http://www.w3.org/2000/svg'
    width={size}
    height={size}
    fill='none'
    viewBox='0 0 131 178'
+   role='img'
+   aria-label='빈 문서 아이콘'
    {...props}
  >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 131 178'
{...props}
>
<svg
xmlns='http://www.w3.org/2000/svg'
width={size}
height={size}
fill='none'
viewBox='0 0 131 178'
role='img'
aria-label='빈 문서 아이콘'
{...props}
>
🧰 Tools
🪛 Biome (2.1.2)

[error] 4-11: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🤖 Prompt for AI Agents
In public/assets/svg/empty-document.tsx around lines 4 to 11, the SVG element
lacks accessible alternative text for screen readers. Add a <title> element
inside the SVG with a descriptive text or include an aria-label attribute on the
SVG element to provide meaningful alternative text for accessibility.

<path
fill='#DDD'
d='M106.397 0c13.255 0 24 10.745 24 24v129.259c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V44.974L44.974 0z'
/>
<path
fill='#79747E'
d='M41.752 2.108c1.895-1.873 5.11-.53 5.11 2.134v18.62c0 13.254-10.746 24-24 24H3.781c-2.68 0-4.015-3.25-2.109-5.134z'
/>
</svg>
);

export default EmptyDocumentIcon;
47 changes: 47 additions & 0 deletions src/apis/reservations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { privateInstance } from './privateInstance';
import {
MyReservationsResponse,
GetMyReservationsParams,
UpdateReservationRequest,
Reservation,
} from '@/types/reservationTypes';

/**
* 내 예약 리스트 조회
* GET /api/reservations
*/
export const getMyReservations = async (
params: GetMyReservationsParams,
): Promise<MyReservationsResponse> => {
const queryParams = new URLSearchParams();

if (params.cursorId) {
queryParams.append('cursorId', params.cursorId.toString());
}
if (params.size) {
queryParams.append('size', params.size.toString());
}
if (params.status) {
queryParams.append('status', params.status);
}

Comment on lines +18 to +29
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

쿼리 파라미터 생성 로직 간소화 가능

조건문을 제거하고 더 간결하게 작성할 수 있습니다.

-  const queryParams = new URLSearchParams();
-
-  if (params.cursorId) {
-    queryParams.append('cursorId', params.cursorId.toString());
-  }
-  if (params.size) {
-    queryParams.append('size', params.size.toString());
-  }
-  if (params.status) {
-    queryParams.append('status', params.status);
-  }
+  const queryParams = new URLSearchParams(
+    Object.entries(params)
+      .filter(([_, value]) => value !== undefined)
+      .map(([key, value]) => [key, String(value)])
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const queryParams = new URLSearchParams();
if (params.cursorId) {
queryParams.append('cursorId', params.cursorId.toString());
}
if (params.size) {
queryParams.append('size', params.size.toString());
}
if (params.status) {
queryParams.append('status', params.status);
}
const queryParams = new URLSearchParams(
Object.entries(params)
.filter(([_, value]) => value !== undefined)
.map(([key, value]) => [key, String(value)])
);
🤖 Prompt for AI Agents
In src/apis/reservations.ts around lines 16 to 27, the query parameter appending
uses multiple if conditions to check each parameter before appending. Simplify
this by iterating over the params object keys and appending only those with
defined values, removing explicit if statements to make the code more concise
and maintainable.

const response = await privateInstance.get(
`/reservations?${queryParams.toString()}`,
);
return response.data;
};

/**
* 내 예약 수정(취소)
* PATCH /api/reservations/{reservationId}
*/
export const updateMyReservation = async (
reservationId: number,
data: UpdateReservationRequest,
): Promise<Reservation> => {
const response = await privateInstance.patch(
`/reservations/${reservationId}`,
data,
);
return response.data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client';

import Modal from '@/components/Modal';
import Button from '@/components/Button';
import CheckIcon from '@assets/svg/check';

interface CancelReservationModalProps {
isOpen: boolean;
onCancel: () => void;
onConfirm: () => void;
isLoading?: boolean;
}

export default function CancelReservationModal({
isOpen,
onCancel,
onConfirm,
isLoading = false,
}: CancelReservationModalProps) {
return (
<Modal isOpen={isOpen} onOpenChange={(open) => !open && onCancel()}>
<Modal.Content className='!h-[184px] !w-[298px] !max-w-none !min-w-0 !rounded-xl !p-0'>
<div
className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16'
style={{
borderRadius: '12px',
background: '#FFFFFF',
boxShadow: '0px 4px 16px 0px rgba(17, 34, 17, 0.05)',
overflow: 'hidden',
}}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

중복된 스타일링 방식 개선 필요

classNamestyle 속성에서 동일한 스타일(borderRadius, background, boxShadow)이 중복 정의되어 있습니다. Tailwind 클래스를 사용하거나 style 객체 중 하나만 사용하는 것이 좋습니다.

-      <Modal.Content className='!h-[184px] !w-[298px] !max-w-none !min-w-0 !rounded-xl !p-0'>
-        <div
-          className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16'
-          style={{
-            borderRadius: '12px',
-            background: '#FFFFFF',
-            boxShadow: '0px 4px 16px 0px rgba(17, 34, 17, 0.05)',
-            overflow: 'hidden',
-          }}
-        >
+      <Modal.Content className='!h-[184px] !w-[298px] !max-w-none !min-w-0 !rounded-xl !p-0 shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)]'>
+        <div className='flex h-full w-full flex-col items-center justify-center gap-24 rounded-xl bg-white p-16'>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Modal.Content className='!h-[184px] !w-[298px] !max-w-none !min-w-0 !rounded-xl !p-0'>
<div
className='flex h-full w-full flex-col items-center justify-center gap-24 bg-white p-16'
style={{
borderRadius: '12px',
background: '#FFFFFF',
boxShadow: '0px 4px 16px 0px rgba(17, 34, 17, 0.05)',
overflow: 'hidden',
}}
<Modal.Content className='!h-[184px] !w-[298px] !max-w-none !min-w-0 !rounded-xl !p-0 shadow-[0px_4px_16px_0px_rgba(17,34,17,0.05)]'>
<div className='flex h-full w-full flex-col items-center justify-center gap-24 rounded-xl bg-white p-16'>
🤖 Prompt for AI Agents
In
src/app/(with-header)/mypage/reservations/components/CancelReservationModal.tsx
around lines 22 to 30, the styles borderRadius, background, and boxShadow are
redundantly defined both in the className and the style attribute. To fix this,
consolidate the styling by either converting all these styles into appropriate
Tailwind CSS classes or by removing them from className and keeping them only in
the style object, ensuring no duplication and consistent styling approach.

>
{/* 체크 아이콘 */}
<div className='flex justify-center'>
<CheckIcon size={24} />
</div>

{/* 메시지 */}
<p className='text-nomad text-center text-lg font-medium'>
예약을 취소하시겠어요?
</p>

{/* 버튼 */}
<div className='flex gap-12'>
<Button
variant='secondary'
className='text-md h-[38px] w-[80px] rounded-lg border border-gray-300 font-medium'
onClick={onCancel}
disabled={isLoading}
>
아니오
</Button>
<Button
variant='primary'
className='text-md bg-nomad h-[38px] w-[80px] rounded-lg font-medium text-white'
onClick={onConfirm}
disabled={isLoading}
>
{isLoading ? '취소 중...' : '취소하기'}
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

이 부분은 팝업 컴포넌트 이용하시면 좋을 것 같습니다! 이미 만들어 둔 부분이라 확인 해보세요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Popup이 아닌 Modal을 선택해서 사용한 이유가 isLoading 상태 처리 및 동적 텍스트가 필요하기에 Popup에는 props에 isLoading?:boolean이 존재하지 않는 반면 Modal 컴포넌트의 경우 isLoading?:boolean이 존재하여 Modal 컴포넌트를 선택해서 사용했습니다.

</div>
</div>
</Modal.Content>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import EmptyDocumentIcon from '@assets/svg/empty-document';

export default function EmptyReservations() {
return (
<div className='flex flex-col items-center justify-center py-[120px]'>
{/* 빈 상태 아이콘 */}
<div className='mb-[24px]'>
<EmptyDocumentIcon size={131} />
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

아이콘 크기 상수화 제안.

하드코딩된 아이콘 크기(131)를 상수로 분리하면 유지보수성이 향상됩니다.

다음과 같이 상수로 분리할 수 있습니다:

+const EMPTY_ICON_SIZE = 131;
+
 export default function EmptyReservations() {
   return (
     <div className='flex flex-col items-center justify-center py-120'>
       {/* 빈 상태 아이콘 */}
       <div className='mb-24'>
-        <EmptyDocumentIcon size={131} />
+        <EmptyDocumentIcon size={EMPTY_ICON_SIZE} />
       </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<EmptyDocumentIcon size={131} />
const EMPTY_ICON_SIZE = 131;
export default function EmptyReservations() {
return (
<div className='flex flex-col items-center justify-center py-120'>
{/* 빈 상태 아이콘 */}
<div className='mb-24'>
<EmptyDocumentIcon size={EMPTY_ICON_SIZE} />
</div>
{/* …rest of the component */}
</div>
);
}
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/EmptyReservations.tsx at
line 8, the icon size is hardcoded as 131. To improve maintainability, define a
constant for the icon size at the top of the file and replace the hardcoded
value with this constant in the EmptyDocumentIcon component.

</div>

{/* 빈 상태 메시지 */}
<p className='text-2xl font-normal text-gray-700'>
아직 등록한 체험이 없어요
</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use client';

import Image from 'next/image';
import Button from '@/components/Button';
import { Reservation } from '@/types/reservationTypes';
import { STATUS_LABELS, STATUS_COLORS } from '@/constants/reservationConstants';
import cn from '@/lib/cn';

interface ReservationCardProps {
reservation: Reservation;
onCancel?: (reservationId: number) => void;
onReview?: (reservationId: number) => void;
}

// 체험 완료 여부 확인 (현재 시간이 체험 종료 시간을 지났는지)
const isExperienceCompleted = (date: string, endTime: string): boolean => {
const experienceEndDateTime = new Date(`${date}T${endTime}+09:00`);
return new Date() > experienceEndDateTime;
};

export default function ReservationCard({
reservation,
onCancel,
onReview,
}: ReservationCardProps) {
const {
id,
activity,
status,
reviewSubmitted,
totalPrice,
headCount,
date,
startTime,
endTime,
} = reservation;

const isCompleted = isExperienceCompleted(date, endTime);
const showCancelButton = status === 'pending';
const showReviewButton = isCompleted && !reviewSubmitted;

return (
<div className='flex h-[204px] w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

반응형 디자인 개선 필요

카드 컨테이너에 고정 너비가 설정되어 있어 작은 화면에서 문제가 발생할 수 있습니다.

-    <div className='flex h-[204px] w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'>
+    <div className='flex h-[204px] w-full max-w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className='flex h-[204px] w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'>
<div className='flex h-[204px] w-full max-w-[792px] overflow-hidden rounded-[24px] border border-gray-300 bg-white'>
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx at
line 43, the card container has a fixed width of 792px which can cause layout
issues on smaller screens. Replace the fixed width with a responsive width
approach, such as using relative units like percentages or max-width, and ensure
the container adapts fluidly to different screen sizes to improve responsive
design.

Copy link
Contributor

Choose a reason for hiding this comment

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

요 부분은 h-204 이런 식으로 사용하셔도 됩니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

감사합니다! 매번 []를 사용할 필요가 없는데 버릇이 되가지고 계속 사용하게 되네요.

{/* 이미지 영역 */}
<div className='relative h-[204px] w-[204px] flex-shrink-0'>
<Image
src={activity.bannerImageUrl}
alt={activity.title}
fill
className='rounded-l-[24px] object-cover'
/>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

이미지 오류 처리 개선 필요

이미지 로딩 실패 시에 대한 처리가 없습니다. 사용자 경험 향상을 위해 오류 처리를 추가하는 것이 좋습니다.

        <Image
          src={activity.bannerImageUrl}
          alt={activity.title}
          fill
          className='rounded-l-[24px] object-cover'
+         onError={(e) => {
+           e.currentTarget.src = '/images/placeholder.jpg'; // 기본 이미지로 대체
+         }}
        />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Image
src={activity.bannerImageUrl}
alt={activity.title}
fill
className='rounded-l-[24px] object-cover'
/>
<Image
src={activity.bannerImageUrl}
alt={activity.title}
fill
className='rounded-l-[24px] object-cover'
onError={(e) => {
e.currentTarget.src = '/images/placeholder.jpg'; // 기본 이미지로 대체
}}
/>
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx
around lines 46 to 51, the Image component lacks error handling for image
loading failures. To improve user experience, add an onError handler to the
Image component that sets a fallback image or handles the error gracefully,
ensuring the UI remains visually consistent even if the original image fails to
load.

</div>

{/* 콘텐츠 영역 */}
<div className='flex flex-1 flex-col justify-start py-[21px] pr-[24px] pl-[24px]'>
{/* 상태 라벨 */}
<div>
<span className={cn('text-lg font-bold', STATUS_COLORS[status])}>
{STATUS_LABELS[status]}
</span>
</div>

{/* 제목 */}
<div className='mt-[8px]'>
<h3 className='text-nomad text-xl font-bold'>{activity.title}</h3>
</div>

{/* 날짜 및 인원 정보 */}
<div className='mt-[12px]'>
<p className='text-2lg text-nomad'>
{date} · {startTime} - {endTime} · {headCount}명
</p>
</div>

{/* 가격 + 버튼 */}
<div className='mt-[21px] flex items-center justify-between'>
{/* 가격 */}
<p className='text-2xl font-bold text-black'>
₩{totalPrice.toLocaleString()}
</p>

{/* 버튼 */}
<div>
{showCancelButton && (
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
>
예약 취소
</Button>
)}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

접근성 개선 필요

버튼에 aria-label이나 더 구체적인 접근성 속성을 추가하면 스크린 리더 사용자에게 더 나은 경험을 제공할 수 있습니다.

              <Button
                variant='secondary'
                className='h-[43px] w-[144px] rounded-md text-lg font-bold'
                onClick={() => onCancel?.(id)}
+               aria-label={`${activity.title} 예약 취소`}
              >
                예약 취소
              </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{showCancelButton && (
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
>
예약 취소
</Button>
)}
{showCancelButton && (
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
aria-label={`${activity.title} 예약 취소`}
>
예약 취소
</Button>
)}
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx
around lines 84 to 92, the cancel button lacks accessibility attributes. Add an
appropriate aria-label to the Button component that clearly describes its
action, such as "예약 취소 버튼" or a more specific label including the reservation
id, to improve screen reader usability.

{showReviewButton && (
<Button
variant='primary'
className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onReview?.(id)}
>
후기 작성
</Button>
)}
</div>
</div>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

버튼 영역 구현 검토

가격 표시와 조건부 버튼 렌더링이 적절히 구현되었습니다. toLocaleString()을 사용한 가격 포맷팅도 좋습니다. 단, 두 버튼이 동시에 표시될 경우 레이아웃을 고려해야 합니다.

버튼들이 동시에 표시될 경우를 대비해 간격 조정이 필요할 수 있습니다:

          <div>
+           <div className='flex gap-2'>
            {showCancelButton && (
              <Button
                variant='secondary'
                className='h-[43px] w-[144px] rounded-md text-lg font-bold'
                onClick={() => onCancel?.(id)}
              >
                예약 취소
              </Button>
            )}
            {showReviewButton && (
              <Button
                variant='primary'
                className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold'
                onClick={() => onReview?.(id)}
              >
                후기 작성
              </Button>
            )}
+           </div>
          </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* 가격 + 버튼 */}
<div className='mt-[21px] flex items-center justify-between'>
{/* 가격 */}
<p className='text-2xl font-bold text-black'>
{totalPrice.toLocaleString()}
</p>
{/* 버튼 */}
<div>
{showCancelButton && (
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
>
예약 취소
</Button>
)}
{showReviewButton && (
<Button
variant='primary'
className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onReview?.(id)}
>
후기 작성
</Button>
)}
</div>
</div>
{/* 가격 + 버튼 */}
<div className='mt-[21px] flex items-center justify-between'>
{/* 가격 */}
<p className='text-2xl font-bold text-black'>
{totalPrice.toLocaleString()}
</p>
{/* 버튼 */}
<div>
<div className='flex gap-2'>
{showCancelButton && (
<Button
variant='secondary'
className='h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onCancel?.(id)}
>
예약 취소
</Button>
)}
{showReviewButton && (
<Button
variant='primary'
className='bg-nomad h-[43px] w-[144px] rounded-md text-lg font-bold'
onClick={() => onReview?.(id)}
>
후기 작성
</Button>
)}
</div>
</div>
</div>
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationCard.tsx
around lines 75 to 103, the price and conditional buttons are rendered
correctly, but when both cancel and review buttons appear together, their layout
spacing needs adjustment. Add appropriate horizontal spacing (e.g., margin or
gap) between the two buttons inside their container div to ensure they don't
appear too close and maintain a clean layout.

</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import Dropdown from '@/components/Dropdown';
import {
FilterOption,
FILTER_OPTIONS,
FILTER_LABELS,
} from '@/constants/reservationConstants';

interface ReservationFilterProps {
value: FilterOption;
onChange: (value: FilterOption) => void;
}

export default function ReservationFilter({
value,
onChange,
}: ReservationFilterProps) {
// 표시용 라벨 옵션 배열
const labelOptions = FILTER_OPTIONS.map((option) => FILTER_LABELS[option]);

// 라벨 선택 시 실제 값으로 변환
const handleChange = (selectedLabel: string) => {
const selectedOption = FILTER_OPTIONS.find(
(option) => FILTER_LABELS[option] === selectedLabel,
);
if (selectedOption !== undefined) {
onChange(selectedOption);
}
};
Comment on lines +23 to +30
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

라벨-옵션 변환 로직에 안전 장치를 추가해보세요.

현재 find 연산이 일치하는 항목을 찾지 못할 경우에 대한 처리가 없습니다. 더 안전한 구현을 위해 예외 상황을 처리하는 것이 좋겠습니다.

const handleChange = (selectedLabel: string) => {
  const selectedOption = FILTER_OPTIONS.find(
    (option) => FILTER_LABELS[option] === selectedLabel,
  );
- if (selectedOption !== undefined) {
+ if (selectedOption) {
    onChange(selectedOption);
+ } else {
+   console.warn(`Unknown filter label: ${selectedLabel}`);
  }
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleChange = (selectedLabel: string) => {
const selectedOption = FILTER_OPTIONS.find(
(option) => FILTER_LABELS[option] === selectedLabel,
);
if (selectedOption !== undefined) {
onChange(selectedOption);
}
};
const handleChange = (selectedLabel: string) => {
const selectedOption = FILTER_OPTIONS.find(
(option) => FILTER_LABELS[option] === selectedLabel,
);
if (selectedOption) {
onChange(selectedOption);
} else {
console.warn(`Unknown filter label: ${selectedLabel}`);
}
};
🤖 Prompt for AI Agents
In src/app/(with-header)/mypage/reservations/components/ReservationFilter.tsx
around lines 23 to 30, the handleChange function does not handle the case when
find returns undefined if no matching label is found. Add a safety check or
fallback to handle this scenario gracefully, such as logging a warning or
ignoring the change, to prevent potential runtime errors.


return (
<Dropdown
options={labelOptions}
value={FILTER_LABELS[value]}
onChange={handleChange}
placeholder='필터'
className='h-[53px] w-[160px]'
disableScroll={true}
/>
);
}
Loading