[react-datepicker 라이브러리] timePicker 개선해보기

D uuu·2024년 2월 20일
0

project

목록 보기
5/10

react-datePicker 라이브러리 사용하면서

react-datePicker 라이브러리를 사용하면서 몇가지 문제를 발견했다.
그 중 오늘은 timePicker 에 대해서 얘기를 해보고 싶다.

홈페이지 바로가기 https://reactdatepicker.com/

화면에 24시간이 보여진다

첫번째는 minTime & maxTime 을 설정해도 화면에는 24시간이 모두 보여진다는 점이다.
보통의 서비스는 비즈니스 시간대가 존재 할 것 이다. 즉 영업 시간이 있고, 그 시간대에서 사용자가 원하는 시간을 고를 수 있도록 하는 것이 일반적이지 않을까?

그러나 react-datePicker 에서 제공하는 timePicker 는 24시간이 전체 렌더링 된다는 특징이 있었다.
물론 정해진 시간대만 선택할 수 있도록 하는 minTime & maxTime 이라는 기능을 제공하고 있는데, 만약 영업시간이 9~21시라고 가정하고 아래와 같이 설정해보자

() => {
  const [startDate, setStartDate] = useState(
    setHours(setMinutes(new Date(), 30), 17),
  );
  return (
    <DatePicker
      selected={startDate}
      onChange={(date) => setStartDate(date)}
      showTimeSelect
      minTime={setHours(setMinutes(new Date(), 0), 9)}
      maxTime={setHours(setMinutes(new Date(), 0), 21)}
      dateFormat="MMMM d, yyyy h:mm aa"
    />
  );
};

👉그럼 아래와 같이 9~21시를 제외한 시간대는 disabled 처리가 된다.
내가 원하는 그림은 화면에 설정한 시간대만 보여지는 건데, 라이브러리는 24시간을 모두 렌더링하고 설정한 시간대만 선택할 수 있도록 그 외 시간대를 disabeld 처리하는 방식으로 구현했다😅

예시1예시2

disabled 처리 안보이게 하기

그렇다면 화면에 disabled 처리 부분을 안보이게 하는 방법이 있진 않을까? 하고 홈페이지를 샅샅히 살펴봤지만, 별도의 옵션이 있지는 않았다.
대신 git issue 에서 이미 다른 개발자가 동일한 문제를 제기한것을 발견했다.

https://github.com/Hacker0x01/react-datepicker/issues/2056

내용을 보니까 disalbed 처리를 숨기거나 사용 가능한 시간대만 표시할 수 있도록 startTime & endTime 이라는 기능이 있었으면 좋겠다는 내용이었다.

그리고 CSS 처리로 disabled 처리를 안보이게 할 수 있다고 누군가 답을 달았는데, 이 방법으로 disabled 처리 된 부분이 안보이게 하는건 가능했다!

그러나 이 방법을 사용하면 선택한 시간대 마저도 화면에서 안보이게 되는 약간의 부작용이 있었다. 아래 사진처럼 만약 누가 9시30분~10시30분 시간을 선택해 이 시간대도 더 이상 선택 하지 못하도록 disalbed 처리가 되면 화면에 그 시간대가 안보이게 된다.

누군가는 이 방법으로도 만족해 할 수 있지만, 사용자 관점에서 봤을때 혼란스럽지 않을까? 갑자기 9시에서 11시로 시간대가 점프하게 되고, 누군가는 10시로 착각해 11시를 예약할 수도 있다.

🤔나는 이 부분이 사용자 경험측면에서 좋지 않다고 생각이 들었고, 앞서 git issue 에 제안한 것 처럼 startTime & endTime 처럼 정해진 시간대만 보여지도록 하는 것이 좋다고 생각이 들었다.


직접 만들자

그래서 라이브러리를 고쳐보려고 여러 시도를 했지만, 결국 내가 원하는 대로 구현하기 위해서는 직접 만드는 방법 밖엔 없다는 생각이 들었다.
그 과정에서 오픈 소스 코드를 분석 해보고 실제 내 코드로 반영한 과정은 아래와 같다.

오픈 소스 분석

소스 코드를 보면 아래 renderTimes 라는 함수를 통해 시간을 렌더링하고 있었다.

  1. activeDate 는 현재 선택된 날짜(selected), 또는 설정된 openToDate 값이 없으면 현재 날짜와 시간을 사용

  2. base를 설정, base는 activeDate의 시작 시간을 나타낸다.

  3. multiplier를 계산, multiplier는 하루에 포함된 분(minutesInDay)을 주어진 간격(intervals)으로 나눈 값을 나타낸다.

  4. for 루프를 사용하여 시간을 생성 for 루프는 multiplier만큼 반복
    각 반복에서 현재 시간을 계산하고(currentTime), 시간 리스트(times)에 추가

  5. 필요한 경우에는 추가적인 시간을 삽입
    sortedInjectTimes에 값이 있으면 해당 시간들을 현재 시간 이후에 삽입

  6. isDisabledTime 함수는 주어진 시간이 비활성화된 시간인지를 확인

  7. isTimeInDisabledRange 함수를 사용하여 주어진 시간이 minTime 또는 maxTime 옵션에 포함되는지 확인
    만약 excludeTimes, includeTimes, 또는 filterTime과 관련된 옵션이 설정되어 있다면, isTimeDisabled 함수를 사용하여 해당 시간이 비활성화되는지를 확인

  isDisabledTime = (time) =>
    ((this.props.minTime || this.props.maxTime) &&
      isTimeInDisabledRange(time, this.props)) ||
    ((this.props.excludeTimes ||
      this.props.includeTimes ||
      this.props.filterTime) &&
      isTimeDisabled(time, this.props));
  renderTimes = () => {
    let times = [];
    const format = this.props.format ? this.props.format : "p";
    const intervals = this.props.intervals;

    const activeDate =
      this.props.selected || this.props.openToDate || newDate();

    const base = getStartOfDay(activeDate);
    const sortedInjectTimes =
      this.props.injectTimes &&
      this.props.injectTimes.sort(function (a, b) {
        return a - b;
      });

    const minutesInDay = 60 * getHoursInDay(activeDate);
    const multiplier = minutesInDay / intervals;

    for (let i = 0; i < multiplier; i++) {
      const currentTime = addMinutes(base, i * intervals);
      times.push(currentTime);

      if (sortedInjectTimes) {
        const timesToInject = timesToInjectAfter(
          base,
          currentTime,
          i,
          intervals,
          sortedInjectTimes
        );
        times = times.concat(timesToInject);
      }
    }

정리하면, 라이브러리는 minTime & maxTime 등과 같은 옵션이 있을경우 함수를 통해 해당 시간대를 비활성화(disabled) 처리한 후, 24시간을 주어진 interval 로 나눠서 시간을 렌더링한다.

수정 코드

  1. generateTimeArray 라는 이름의 함수로, 시간 간격(timeInterval)과 선택된 날짜(selectedDate)를 매개변수로 받는다.

  2. 먼저, 24시간(1440분)을 총 분 단위로 나타내는 totalMinutes 변수를 설정

  3. currentMinute 변수를 사용하여 현재 분을 추적하고, 0부터 totalMinutes까지의 범위에서 반복문을 실행

  4. 각 반복에서, 현재 분을 시간과 분으로 변환하여 시간 형식을 만든다.

  5. 생성된 시간을 시간 목록에 추가합니다. 이때, 시간은 value로, '시:분' 형식의 문자열은 label로, 선택 가능 여부는 selectable로 설정

  1. 생성된 데이터는 아래와 같다.

  1. minTime & maxTime 옵션이 있을때 filter 함수를 사용해 minTime <= timeList <= maxTime 에 해당 되는 timeList 를 구한다.
export const generateTimeArray = (interval: number, date: Date) => {
  const result = [];

  const totalMinutes = 24 * 60;
  let currentMinute = 0;

  while (currentMinute < totalMinutes) {
    const hour = Math.floor(currentMinute / 60);
    const minute = currentMinute % 60;
    const formattedHour = hour.toString().padStart(2, "0");
    const formattedMinute = minute.toString().padStart(2, "0");

    const formattedDate = new Date(date).setHours(
      Number(formattedHour),
      Number(formattedMinute),
      0
    );
    const dateLabel = new Date(formattedDate);

    const timeObject = {
      value: dateLabel,
      label: `${formattedHour}:${formattedMinute}`,
      selectable: true,
    };
    result.push(timeObject);
    currentMinute += interval;
  }

  return result;
};
  let timeList = generateTimeArray(timeInterval, selected);

  if (minTime) {
    timeList = timeList.filter((time) => time.label >= minTime);
  }

  if (maxTime) {
    timeList = timeList.filter((time) => time.label <= maxTime);
  }

  if (excludeTimes) {
    timeList = adjustSelectable(timeList, excludeTimes);
  }

최종 화면

최종 구현한 화면은 아래와 같다.
영업 시간으로 설정된 시간대(9시부터 21시까지)만을 화면에 보여지고, 선택이 불가능한 시간대는 비활성화 처리된다. 이를 통해 사용자는 예약 가능한 시간대를 명확하게 확인이 가능하다!!

정리

라이브러리를 사용하면서 한계를 많이 느꼈다. 내 필요에 맞게 수정하는 데 있어서 실력도 많이 부족했고, 실제로 오픈 소스를 수정해보고 싶었으나 시간적 제약으로 그러지 못했다. 그럼에도 불구하고, 이렇게 까지 라이브러리를 뜯어보고 분석해본 적이 없었기에 새로운 경험도 하고 공부도 많이 되었던 것 같다.

내가 만든 코드가 완벽하지는 않지만, 라이브러리를 분석하며 문제를 해결하는 과정은 너무 재미있었다!!! 라이브러리를 고쳐서 해결했더라면 더 좋았겠지만, 언젠가는 오픈 소스를 고쳐서 다른 개발자에게 도움도 되고 기여해보고 싶단 생각이 든다!!! 😁😁

profile
배우고 느낀 걸 기록하는 공간

0개의 댓글

관련 채용 정보