Frond-end/React

[React] datepicker 적용해보기

Nellie Kim 2024. 2. 21. 16:27
728x90

next.js 와 react.js 로 개발하며 적용한 부분이다. 

 

아래와 같이 모바일 화면 안에서 예약 일시에 datepicker 를 사용하여 입력받고 싶었다. 

 

기획 상으로 부터 요청 받은 적용 조건 사항은 아래와 같다.

 

원하는 달력 적용 조건

- 이전 달, 다음 달은 회색 처리하여 표시되도록 할 것

- 달력을 선택하기 전에 오늘 날짜가 표시되도록 할 것

- 오늘 이전으로는 선택하지 못하도록 할 것

- 한달 뒤는 선택하지 못하도록 할 것

 

타임피커 적용 조건

- 09~24시만 적용되도록 할 것

- 30분 단위로 선택하도록 할 것

 

리액트에서 제공하는 datepicker 라는 라이브러리를 사용하면 아주 편하게 적용할 수 있다. 

 

 

설치

먼저 터미널에 아래 명령어를 입력해주고 설치해 준다. 

npm install react-datepicker --save
npm install --save @types/react-datepicker

 

 

import 

쓰고자 하는 파일 위에 임포트 해준다.

import ReactDatePicker from "react-datepicker";
import 'react-datepicker/dist/react-datepicker.css';

 

 

이제 원하는 디자인을 아래 사이트에서 골라서 적용해주면 된다.

https://reactdatepicker.com/ 

 

 

전체 코드 

/* eslint-disable @next/next/no-img-element */

import React, { useState, useRef, useEffect } from "react";
import { useRouter } from "next/router";
import { format, toDate, addMonths  } from "date-fns";
import { getDetail } from "@apis/storeApi";
import Layout from "@comps/Layout";
import ReactDatePicker from "react-datepicker"; // 추가!
import 'react-datepicker/dist/react-datepicker.css'; // 추가!

function DetailPage() {
  /**
|--------------------------------------------------
| 😃 1. 일반 변수
|--------------------------------------------------
*/

  const router = useRouter();
  const { idx } = router.query;
  const [data, setData] = useState<any>(); // 상세조회 data
  const datepickerRef = useRef(null);
  const [selectedDate, setSelectedDate] = useState<Date>(new Date()); // 현재 날짜를 디폴트 값으로!
  const datepickerRef = useRef(null);
  const [selectedTime, setSelectedTime] = useState<any>();
  const [selectedDate, setSelectedDate] = useState<Date>(new Date());
  
  const minTime = new Date();
  minTime.setHours(9, 0); // 예약 일시 09시부터~
  
  const [formattedTime, setFormattedTime] = useState<Date>(minTime);

  const maxTime = new Date();
  maxTime.setHours(23, 59); // ~ 24시까지

  /**
|--------------------------------------------------
| 🔑 2. 일반 함수
|--------------------------------------------------
*/

  // time 이벤트 핸들러
  const handleTimeChange = (selectedTime: any) => {
    const hours = selectedTime.getHours();
    const minutes = selectedTime.getMinutes();
    const fTime = `${hours}:${minutes < 10 ? '0' : ''}${minutes}`;// 출력: "12:00"

    const timeStringToDate = (timeString: string): Date => {
      const today = new Date();
      const [hours, minutes] = timeString.split(':').map(Number);
      const dateWithTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), hours, minutes);
      return dateWithTime;
    };
    const st = timeStringToDate(fTime); // timepicker 박스에 표현해줄 변수
    setFormattedTime(st)
    console.log("st",st); //Wed Feb 21 2024 12:30:00 GMT+0900 

    console.log("selectedTime:", selectedTime); // "12:00"
  };

  /**
|--------------------------------------------------
| 🎁 3. fetch 함수 
|--------------------------------------------------
*/
  /**3-1. 상단의 상점 정보 조회 */
  async function fetchData(idx: any) {
    const request = {
      idx: Number(idx),
    };

    const response = await getDetail(request);

    if (response.status == 200) {
      // 성공하면 데이터 세팅..
      setData(response.data);
    }
  }
  
  /**
|--------------------------------------------------
| ⚡ 5. useEffect
|--------------------------------------------------
*/
  /**  목록 조회 GET */
  useEffect(() => {
    fetchData(idx);
  }, [idx]); // idx가 변경될 때마다 fetchData() 호출

  /*
|--------------------------------------------------
| 🎨 화면
|--------------------------------------------------
*/

  return (
    <Layout
      title={"예약"}
      backUrl="/member/reserve/list"
    >
      <div className="section white-type">
        <div className="wrapper">
          <div className="writing-box">
            <div className="title-wrap sub">
              <div className="title">예약 일시</div>
            </div>
            <div className="row-half">
              <div className="datepicker" >
                <div className="datepicker" >
                  <ReactDatePicker
                    ref={datepickerRef}
                    shouldCloseOnSelect
                    dateFormat="yyyy-MM-dd"
                    placeholderText="선택하세요"
                    id="datepicker1"
                    selected={selectedDate} // 선택된 날짜를 ReactDatePicker에 전달
                    onChange={(date) => setSelectedDate(date as Date)}
                    minDate={new Date()} // 오늘 이전의 날짜 선택 불가능하게 설정
                    maxDate={addMonths(new Date(), 1)} // 한 달 후의 날짜 선택 불가능하게 설정
                  />
                </div>
              </div>
               <div className="datepicker" >
                  <ReactDatePicker
                    ref={datepickerRef}
                    shouldCloseOnSelect
                    placeholderText="선택하세요"
                    id="datepicker2"
                    selected={formattedTime}
                    showTimeSelect
                    showTimeSelectOnly
                    timeIntervals={30}
                    timeCaption="Time"
                    dateFormat="HH:mm"
                    minTime={minTime} // 09:00부터 선택 가능하도록 설정
                    maxTime={maxTime} // 24:00까지 선택 가능하도록 설정
                    onChange={(selectedTime) => handleTimeChange(selectedTime)}
                  />
                </div>
            </div>
          </div>
        </div>
      </div>
    </Layout>
  );
}

export default DetailPage;

 

 

CSS 적용

두가지 CSS 커스텀을 style.scc 에 적용했다. 

 

1. 기본 화면은 해당 월이 아닌 이전 월, 다음 월의 숫자가 검정색으로 보여지기 때문에, 가독성을 위해 해당월만 검정 숫자로 표현되고, 다른 월의 날짜는 회색으로 표현되도록 추가했다. 

 

2. 두번째로는 달력이 자꾸 화면 밖으로 나가서 사용이 불편해서 추가했다. 예쁘게 화면 안으로 잘 들어온다.

 

/* reserve/[idx] 화면의 datepicker의 추가 설정 (보여지는달력 해당월아닌 다른월 날짜는 회색컬러로 글자처리)  */
.react-datepicker__day--outside-month {
  color: #a8a8a8 !important;
  pointer-events: none;
} 

/* 달력이 화면 밖으로 나가서 밖으로 나가지 않도록 적용*/
.react-datepicker-popper{
  width: 50%;
}

 


 

조금 삽질하긴 했지만, 최종 위 코드로 완벽하게 구현했다.

datepicker로 전달받은 날짜를 포맷팅해서 DB 저장까지 완료했다.

 

 

뿌듯!!