MapleStory Finger Point

๐Ÿ”˜ ํ”„๋กœ์ ํŠธ

[Project] React Calendar ํ™œ์šฉํ•˜๊ธฐ (Date, Time, disabled)

HYEJU01 2025. 2. 7. 21:57
 

๋ชฉ์ฐจ

 

     

     

     

     

     

     

     

    ์œ„์™€ ๊ฐ™์ด ๋ฆฌ์•กํŠธ ์บ˜๋ฆฐ๋”๋ฅผ ์ด์šฉํ•ด์„œ ๋‚ ์งœ๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ์‹œ๊ฐ„์„ ๋ฐ›์•„ ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

    ํŠน์ • ๋‚ ์งœ๋Š” ๋น„ํ™œ์„ฑํ™” ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ๋„ ์žˆ๋‹ค.

    (์šฐ๋ฆฌ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ์˜ˆ์•ฝ์ด ๊ฝ‰์ฐจ๊ฑฐ๋‚˜, ํœด๋ฌด์ผ์ด๊ฑฐ๋‚˜ , ์ง€๊ธˆ์œผ๋กœ๋ถ€ํ„ฐ 3๊ฐœ์›” ๋‚ด ์ด์™ธ์˜ ๊ฐ’์€ disabled ๋œ๋‹ค.)

     

     

     

     


    1) ๋ฆฌ์•กํŠธ ์บ˜๋ฆฐ๋” ์„ค์น˜ (install)

     

    npm install react-calendar

     

     


     

    2) ๋ฆฌ์•กํŠธ ์บ˜๋ฆฐ๋” ์‚ฌ์šฉ (import)

     

    import Calendar from 'react-calendar';

     

    import "./Calendar.css";

     

     

    `react-calendar` ์™€ `css` import ์‹œ์ผœ์ฃผ๊ธฐ

     

     

     


     

     

    3) ๋‚ ์งœ ๊ฐ€์ ธ์˜ค๊ธฐ (Date seleted)

     

          <Calendar
            onChange={handleDateChange}
            value={date}
            onClickDay={handleDateClick} 
            locale="en-US" // ๋ฏธ๊ตญ ๋กœ์ผ€์ผ ์„ค์ • (์ผ์š”์ผ๋ถ€ํ„ฐ ์‹œ์ž‘)
            next2Label={null}
            prev2Label={null}
            tileDisabled={tileDisabled}
            tileClassName={getTileClass}
            formatDay={(locale, date) => moment(date).format('D')} 
            formatShortWeekday={(locale, date) =>
                    ['์ผ', '์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† '][date.getDay()]
                  } 
            formatMonthYear={(locale, date) =>
                     `${moment(date).format('YYYY๋…„ MMM')}`
                   } 
          />

     

    Calendar ์ปดํฌ๋„ŒํŠธ์— ์œ„์™€ ๊ฐ™์ด ๊ธฐ๋ณธ Props ์†์„ฑ๋“ค์„ ์ฃผ๋ฉด ๋˜๋Š”๋ฐ

     

    • `value` ์„ ํƒ๋œ ๋‚ ์งœ ๋˜๋Š” ๋‚ ์งœ ๋ฐฐ์—ด์„ ์„ค์ •
    • `onChange` ๋‚ ์งœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜
    • `onClickDay` ํŠน์ • ๋‚ ์งœ๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜
    • `locale` ๋กœ์ผ€์ผ ์„ค์ • (์˜ˆ: "ko-KR" ํ•˜๋ฉด ํ•œ๊ธ€๋กœ ํ‘œ์‹œ๋จ)
      locale="en-US" // ๋ฏธ๊ตญ ๋กœ์ผ€์ผ ์„ค์ • (์ผ์š”์ผ๋ถ€ํ„ฐ ์‹œ์ž‘)

     

    • `next2Label` ๋‹ค์Œ 2๋…„ ์ด๋™ ๋ฒ„ํŠผ (๊ธฐ๋ณธ๊ฐ’: », null๋กœ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Œ)
    • `prev2Label` ์ด์ „ 2๋…„ ์ด๋™ ๋ฒ„ํŠผ (๊ธฐ๋ณธ๊ฐ’: «, null๋กœ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Œ)

     

    • `formatDay` , `formatShortWeekday` , `formatMonthYear`, ` formatYear` ๋“ฑ์œผ๋กœ ์ปค์Šคํ…€ ํฌ๋งท์œผ๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋‹ค.
    formatDay={(locale, date) => moment(date).format('D')} 
    // date ๊ฐ์ฒด๋ฅผ moment.js ํ˜•ํƒœ๋กœ ๋ฐ”๊ฟ”ใ…“์„œ 1,2,3 (01,02 x )  ํ˜•ํƒœ๋กœ ํ‘œ๊ธฐ
    
    formatShortWeekday={(locale, date) =>
            ['์ผ', '์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† '][date.getDay()]
          } // ์š”์ผ์„ ํ•œ๊ตญ์–ด๋กœ ํ‘œ์‹œ
                  
    formatMonthYear={(locale, date) =>
         `${moment(date).format('YYYY๋…„ MMM')}`
       } // ์›”์„ ํ•œ๊ตญ์–ด๋กœ ํ‘œ์‹œ

     

    • `tileDisabled`, `tileClassName`, `tileContent` ์œผ๋กœ ํŠน์ • ๋‚ ์งœ๋ฅผ ์Šคํƒ€์ผ๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค. 
            tileDisabled={tileDisabled}
            tileClassName={getTileClass} // ๋‚ ์งœ ์Šคํƒ€์ผ๋ง

     

     

    ์ด์™ธ์—๋„

    • `onClickMonth` ํŠน์ • ์›”์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜
    • `onClickYear` ํŠน์ • ์—ฐ๋„๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜
    • `onActiveStartDateChange` ํ˜„์žฌ ๋ณด์ด๋Š” ๋‚ ์งœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์‹คํ–‰ํ•  ํ•จ์ˆ˜ 
    • `minDate` ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ์†Œ ๋‚ ์งœ
    • `maxDate` ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋‚ ์งœ
    • `view` ๋ชจ๋“œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. `month`, `year`, `decade`, `century`
    • ` selectRange` ๋‹ค์ค‘ ๋‚ ์งœ ์„ ํƒ๋„ ๊ฐ€๋Šฅ  `selectRange={true}`

     

     

     

     


     

    4) ๋‚ ์งœ ๋ง‰๊ธฐ (Date disabled)

     

    ๋‚ ์งœ๋ฅผ ๋ง‰๋Š” ๋ฐฉ๋ฒ•์€ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ

     

     

     

    1. ํ•˜๋‚˜๋Š” ์˜ค๋Š˜๋กœ๋ถ€ํ„ฐ 3๊ฐœ์›” ๋’ค์˜ ๋‚ ์งœ๋งŒ ๋ณด์—ฌ์ฃผ๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹ →  `tileDisabled` ์†์„ฑ ์‚ฌ์šฉ

      // ๋‚ ์งœ ๋น„ํ™œ์„ฑํ™” 
      const  tileDisabled = ({ date }) => {
      
        const today = new Date();
        const threeMonthsLater = new Date();
        threeMonthsLater.setMonth(today.getMonth() + 3); // ์ง€๊ธˆ๋ถ€ํ„ฐ 3๊ฐœ์›” ๋’ค
        
        // ์˜ค๋Š˜๊ณผ 3๊ฐœ์›” ํ›„์˜ ๋‚ ์งœ ์‚ฌ์ด์˜ ๋‚ ์งœ๋Š” ๋น„ํ™œ์„ฑํ™” ํ•˜์ง€ ์•Š์Œ
        return date < today || date > threeMonthsLater;
      };

     

     

     

     

    2. ์˜ˆ์•ฝ์ด ๊ฝ‰์ฐฌ ๊ฒฝ์šฐ disabled ์‹œํ‚ค๋Š” ๊ฒฝ์šฐ →  `tileClassName` ์†์„ฑ ์‚ฌ์šฉ

     

     

    tileClassName={getTileClass}

     

    className ์„ ์ถ”๊ฐ€ํ•ด์„œ css ๋จน์ด๊ฒŒ ํ•ด์„œ ์˜ˆ์•ฝ์ด ๋ง‰ํžŒ ๊ฒƒ ์ฒ˜๋Ÿผ ํ‘œํ˜„ํ•จ.

     

      const getTileClass = ({ date }) => {
        const formattedDate = date.toLocaleDateString('en-CA'); // 'yyyy-mm-dd' ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ (๋กœ์ปฌ ์‹œ๊ฐ„ ๊ธฐ์ค€)
        
    	// (... ๊ณ ์ •ํœด๋ฌด์— ๋Œ€ํ•œ ์ฝ”๋“œ)
      
        // ์˜ˆ์•ฝ ์ •๋ณด ํ™•์ธ
        const slot = dateTime2.find((slot) => slot.reservationSlotDate === formattedDate); // ํ•ด๋‹น ๋‚ ์งœ์˜ ์˜ˆ์•ฝ ์ •๋ณด ์ฐพ๊ธฐ
        if (slot) {
          if (slot.slotStatusCount === slot.slotCount) {
            return 'reserved'; // ์˜ˆ์•ฝ์ด ๋ชจ๋‘ ์ฐผ๋‹ค๋ฉด 'reserved' 
          }
        }
      
        return ''; // ์˜ˆ์•ฝ์ด ์ฐผ์ง€ ์•Š๊ฑฐ๋‚˜ slot์ด ์—†์œผ๋ฉด ๋นˆ ๋ฌธ์ž์—ด ๋ฐ˜ํ™˜
      };

     

     

     

     

     



    5) ์‹œ๊ฐ„ ๊ฐ€์ ธ์˜ค๊ธฐ (Time Selected)

     

     

    1. ์˜ˆ์•ฝ ๊ฐ€๋Šฅ  (์‹œ๊ฐ„ ์ถœ๋ ฅ)

     

      {dateTime.map((slot) => (
                !noSlotsMessage ? (
                  <div key={slot.reservationSlotKey}>
                    <div className="user-reserve-date-time">
                      {timeSlots.map((timeSlot, index) => (
                        <button
                          key={index}
                          type="button"
                          onClick={() => {
                            handleSlotClick(index); // ์Šฌ๋กฏ ์„ ํƒ ๊ด€๋ฆฌ
                            handleSlotClick2(timeSlot.time); // ์Šฌ๋กฏ ์‹œ๊ฐ„ ์ถœ๋ ฅ
                            handleSlotClick3(slot.reservationSlotKey);
                          }}
                          // ์•„๋ž˜ ์‹œ๊ฐ„ ๊ด€๋ จ ์†์„ฑ์€ ์ด์ œ ์•ˆ์”€.. (์‹œ๊ฐ„ ๊ด€๋ฆฌ๊นŒ์ง„ ์—†์•ฐ)
                          //disabled={disabledTimes.includes(timeSlot.time)} // ๋น„ํ™œ์„ฑํ™” ์กฐ๊ฑด ์ถ”๊ฐ€
                          //style={{
                            //backgroundColor: selectedSlot === index ? '#fd8517' : 'transparent',
                            //color: selectedSlot === index ? 'white' : 'black',
                          //</div></div> opacity: disabledTimes.includes(timeSlot.time) ? 0.5 : 1, // ๋น„ํ™œ์„ฑํ™”๋œ ์Šฌ๋กฏ์˜ ํˆฌ๋ช…๋„ ์กฐ์ ˆ
                          //}}
                        >
                          {timeSlot.time} {/* ์Šฌ๋กฏ ์‹œ๊ฐ„ ํ‘œ์‹œ */}
                        </button>
                      ))}
                    </div>
                  </div>
                ) : (
                  <div className='msg1'>
                    ์˜ˆ์•ฝ ๊ฐ€๋Šฅํ•œ ์‹œ๊ฐ„์ด ์—†์Šต๋‹ˆ๋‹ค. <br /> ๋‹ค๋ฅธ ๋‚ ์งœ๋ฅผ ์„ ํƒํ•ด์ฃผ์„ธ์š”.
                  </div>
                )
            ))}

     

     

    ํƒ€์ž„๋‹น ์˜ˆ์•ฝ์ด ์ด๋ฏธ ๊ฑธ๋ ค์žˆ๋Š” ๊ฒฝ์šฐ ์˜ˆ์•ฝ์ด ๋ถˆ๊ฐ€ํ•˜๋„๋ก (1ํƒ€์ž„ = 1์˜ˆ์•ฝ) ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

    ํ•˜์ง€๋งŒ, ํŠน์ • ํƒ€์ž„์— ์—ฌ๋Ÿฌ ์˜ˆ์•ฝ์„ ๋ฐ›๊ฒŒํ•˜๊ณ  ์˜ˆ์•ฝ์ด ๋ชฐ๋ฆฌ๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ฐ€๊ฒŒ์‚ฌ์žฅ๋‹˜์ด ์ฃผ๋ฌธ์ทจ์†Œ๋กœ ์ž˜ ๋Œ€์‘ํ•˜๋Š” ์ชฝ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ฒŒ๋˜์—ˆ๋‹ค.

    (์‹œ๊ฐ„์„ ๋ง‰๋Š” ๊ธฐ๋Šฅ์€ ๋บ์ง€๋งŒ ์•„๋ฌดํŠผ ๊ตฌํ˜„ ํ•ด๋ณธ ๊ฒฐ๊ณผ ์‹œ๊ฐ„์„ ๋ง‰์„ ์ˆ˜๋„ ์žˆ์Œ)

     


     

     

     

    2. ์˜ˆ์•ฝ ๋ถˆ๊ฐ€ (์˜ˆ์•ฝ ๋ถˆ๊ฐ€ ๋ฉ”์„ธ์ง€ ๋„์›€)

     

     onClickDay={handleDateClick}
        const handleDateClick = (selectedDate) => {
          setDate(selectedDate);
          const isReserved = getTileClass({ date: selectedDate }) === 'reserved';
          setNoSlotsMessage(isReserved); // 'reserved' ๋‚ ์งœ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์˜ˆ์•ฝ ๋ถˆ๊ฐ€ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
          if (isReserved) {
            console.log('์˜ˆ์•ฝ๋ถˆ๊ฐ€');
          }
        };

     

    ์บ˜๋ฆฐ๋” ์ปดํฌ๋„ŒํŠธ์— Day Click ์ด๋ฒคํŠธ์— ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ๊ฑธ์–ด์ค€๋‹ค.