Skip to content
8 changes: 7 additions & 1 deletion src/components/common/input/calendar-filter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export interface CalendarFilterProps {
onChange: (date: Date) => void;
}

export default function CalendarFilter({ value, toDoDates, onChange }: CalendarFilterProps) {
export default function CalendarFilter({
value,
toDoDates,
onChange,
...rest
}: CalendarFilterProps) {
Comment on lines +15 to +20
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Props 타입 정의 개선이 필요합니다.

CalendarFilterProps 인터페이스에 추가된 rest props의 타입이 정의되어 있지 않습니다. 타입 안정성을 위해 다음과 같은 개선이 필요합니다.

export interface CalendarFilterProps {
  value: Date;
  toDoDates: Date[] | null;
  onChange: (date: Date) => void;
+ // Calendar 컴포넌트의 props 타입을 확장
+ extends Omit<CalendarProps, 'value' | 'onChange'>;
}

Committable suggestion skipped: line range outside the PR's diff.

const [selected, setSelected] = useState<Date>(value);

const handleSelect = (date: Date) => {
Expand Down Expand Up @@ -69,6 +74,7 @@ export default function CalendarFilter({ value, toDoDates, onChange }: CalendarF
</Indicator>
);
}}
{...rest}
/>
</DatesProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default meta;
const Template: StoryFn<DateTimePickerProps> = function DateTimePickerStory() {
const [date, setDate] = useState(new Date());

return <DateTimePicker fullDate={date} onChange={setDate} />;
return <DateTimePicker fullDate={date} onChange={(newValue) => setDate(new Date(newValue))} />;
};

export const DateTimePicker01 = Template.bind({});
Expand Down
14 changes: 11 additions & 3 deletions src/components/common/input/date-time-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import IconArrow from '@/public/assets/icons/ic-arrow';

export interface DateTimePickerProps {
fullDate: Date;
onChange: (date: Date) => void;
onChange: (date: string) => void;
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Date 타입 변경으로 인한 영향도가 확인되었습니다

현재 DateTimePicker 컴포넌트를 사용하는 두 곳에서 모두 Date 객체를 사용하고 있습니다:

  • date-time-picker.stories.tsx: useState<Date>() 사용
  • create-gathering-form/index.tsx: new Date()를 fullDate prop으로 전달

이 변경으로 인해 다음 작업이 필요합니다:

  • stories 파일의 state 타입과 예제 코드 수정
  • create-gathering-form의 date 처리 로직 수정
🔗 Analysis chain

인터페이스 변경에 대한 마이그레이션 가이드가 필요합니다.

onChange 콜백의 타입을 Date에서 string으로 변경하는 것은 Breaking Change입니다. 이 컴포넌트를 사용하는 모든 곳에서 수정이 필요할 것 같습니다.

다음 스크립트로 영향을 받는 컴포넌트들을 확인해보겠습니다:


Let me check the actual usage of DateTimePicker in these files to understand the impact.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# DateTimePicker 컴포넌트의 사용처를 검색
rg -l "DateTimePicker" src/

Length of output: 249


Script:

#!/bin/bash
# Check how DateTimePicker is being used in the stories and actual implementation
rg "DateTimePicker" src/components/common/input/date-time-picker/date-time-picker.stories.tsx -A 5
rg "DateTimePicker" src/app/\(crew\)/crew/detail/\[id\]/_components/create-gathering/create-gathering-form/index.tsx -A 5

Length of output: 1473

}

export default function DateTimePicker({ fullDate, onChange }: DateTimePickerProps) {
Expand All @@ -26,9 +26,17 @@ export default function DateTimePicker({ fullDate, onChange }: DateTimePickerPro
};

const handleTime = () => {
const newDate = dayjs(selected).hour(Number(hour)).minute(Number(minute)).toDate();
const newDate = new Date(selected);

newDate.setHours(Number(hour));
newDate.setMinutes(Number(minute));

const kstDate = new Date(newDate.getTime() - newDate.getTimezoneOffset() * 60000).toISOString();

setSelected(newDate);
onChange(newDate);

// KST 기준 ISO 문자열 전달
onChange(kstDate);
Comment on lines +29 to +39
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

시간대 처리 로직 개선이 필요합니다.

현재 구현에서 몇 가지 개선이 필요한 부분이 있습니다:

  1. 수동으로 KST 변환하는 대신 dayjs를 활용하면 더 안전하게 처리할 수 있습니다.
  2. 자정 근처에서 날짜가 변경될 수 있는 엣지 케이스 처리가 필요합니다.

다음과 같이 개선하는 것을 제안드립니다:

-    const newDate = new Date(selected);
-
-    newDate.setHours(Number(hour));
-    newDate.setMinutes(Number(minute));
-
-    const kstDate = new Date(newDate.getTime() - newDate.getTimezoneOffset() * 60000).toISOString();
-
-    setSelected(newDate);
-
-    // KST 기준 ISO 문자열 전달
-    onChange(kstDate);
+    const newDate = dayjs(selected)
+      .hour(Number(hour))
+      .minute(Number(minute))
+      .tz('Asia/Seoul');
+    
+    setSelected(newDate.toDate());
+    onChange(newDate.toISOString());

이렇게 수정하면:

  • dayjs의 타임존 기능을 활용하여 더 안정적인 시간 처리가 가능합니다
  • 날짜 변경선 근처에서의 엣지 케이스도 자동으로 처리됩니다

Committable suggestion skipped: line range outside the PR's diff.

};

return (
Expand Down
50 changes: 33 additions & 17 deletions src/components/common/input/pop-over-calendar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Button, Popover } from '@mantine/core';
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import CalendarFilter from '@/src/components/common/input/calendar-filter';
Expand All @@ -11,46 +11,62 @@ export interface PopOverProps {
}

export default function PopOverCalendar({ value, onChange }: PopOverProps) {
const [opened, { close, open }] = useDisclosure(false);
const [inputTheme, setInputTheme] = useState('white');
const [opened, { open, close }] = useDisclosure();
const [inputTheme, setInputTheme] = useState(opened ? 'dark' : 'light');
const [date, setDate] = useState<Date>(value);
const isMobile = useMediaQuery(`(max-width: ${theme.breakpoints.md})`);
const popOver = useRef<HTMLDivElement>(null);

const handleClear = () => {
const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setDate(new Date());
onChange(new Date());
close();
};
const handleSubmit = () => {
const handleSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
onChange(date);
close();
};

const handleOutsideClick = (e: MouseEvent) => {
if (popOver.current && !popOver.current.contains(e.target as Node)) {
close();
}
};
Comment on lines +20 to +35
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

초기화 동작 개선 필요

handleClear 함수에서 현재 날짜로 초기화하는 것보다 날짜를 완전히 비우는 것이 사용자 경험에 더 적합할 것 같습니다.

다음과 같이 수정하는 것을 제안합니다:

const handleClear = (e: React.MouseEvent<HTMLButtonElement>) => {
  e.stopPropagation();
-  setDate(new Date());
-  onChange(new Date());
+  setDate(null);
+  onChange(null);
  close();
};

이를 위해 PopOverProps 인터페이스도 수정이 필요합니다:

export interface PopOverProps {
-  value: Date;
+  value: Date | null;
-  onChange: (date: Date) => void;
+  onChange: (date: Date | null) => void;
}

Committable suggestion skipped: line range outside the PR's diff.

useEffect(() => {
document.addEventListener('click', handleOutsideClick);
return () => document.removeEventListener('click', handleOutsideClick);
}, []);
Comment on lines +36 to +39
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

의존성 배열 개선 필요

useEffect의 의존성 배열에 close 함수가 누락되어 있어 스테일 클로저 문제가 발생할 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

useEffect(() => {
  document.addEventListener('click', handleOutsideClick);
  return () => document.removeEventListener('click', handleOutsideClick);
-}, []);
+}, [close]);

Committable suggestion skipped: line range outside the PR's diff.

return (
<Popover
withinPortal
opened={opened}
onClose={close}
closeOnClickOutside
closeOnEscape
clickOutsideEvents={['click', 'touchstart']}
width={110}
position={isMobile ? 'bottom' : 'bottom-start'}
shadow="md"
opened={opened}
>
<Popover.Target>
<Button
type="button"
onFocus={() => {
setInputTheme('dark');
if (!opened) open();
}}
onBlur={() => {
setInputTheme('white');
if (opened) close();
}}
onClick={open}
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"
>
<span>날짜 선택</span>
<span>{date ? `${date.getMonth() + 1}월 ${date.getDate()}일` : '날짜 선택'}</span>
<IconArrow direction="down" color={`${inputTheme === 'dark' ? '#ffffff' : '#D1D5DB'}`} />
</Button>
</Popover.Target>
<Popover.Dropdown w={336} h={386} px={43} py={24} className="rounded-xl shadow-xl">
<Popover.Dropdown
w={336}
h={386}
px={43}
py={24}
ref={popOver}
className="rounded-xl shadow-xl"
>
<div className="flex flex-col gap-4">
<CalendarFilter value={date} toDoDates={null} onChange={setDate} />
<div className="flex justify-between gap-3">
Expand Down