reactで綺麗？でカスタマイズできるカレンダー

## 初めに
　reactで綺麗でカスタマイズできるカレンダーを作ろうと挑戦して出来上がったもの残します。
　なにせ初心者の６６歳のおじいちゃん作ですので、効率性が悪かったり、不要なコードがあると思いますので、お許しください。

 ## 構成はreactのCalendar.jsとcalendar.scssです。
 日付を押すと、その日のデータが取得できます。
 左上の「２０２５／１」のところをクリックすると、年の選択ができます。

 | 項番 | ページ内リンク |
|:----:|:-------------|
|1|[完成品](#完成品)|
|2|[基本的な構成？](#基本的な構成)|
|3|[書き込み等ができるコード](#書き込み等ができるコード)|
|4|[カレンダースタイル](#カレンダースタイル)|


 ## 完成品
 ![スクリーンショット 2025-01-08 161416.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3344967/2876b8d9-70e8-22db-2f86-9390cf787f64.png)

## 基本的な構成
 ```:calendar.js
 import React, { useEffect, useState } from "react";
import "./Calendar.scss";

const Calendar = () => {
  const [year, setYear] = useState(new Date().getFullYear());
  const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
  const [month, setMonth] = useState(new Date().getMonth());
  const [holidays, setHolidays] = useState({});
  const [showModal, setShowModal] = useState(false);
  const [selectDay, setSelectDay] = useState("");

  const getDaysInMonth = (year, month) => {
    return new Date(year, month + 1, 0).getDate();
  };

  const getFirstDayOfMonth = (year, month) => {
    return new Date(year, month, 1).getDay();
  };

  const getPrevMonthDays = (year, month) => {
    const prevMonth = month === 0 ? 11 : month - 1;
    const prevMonthYear = month === 0 ? year - 1 : year;
    const prevMonthDays = getDaysInMonth(prevMonthYear, prevMonth);
    const prevMonthFirstDay = getFirstDayOfMonth(prevMonthYear, prevMonth);
    return { prevMonthDays, prevMonthFirstDay };
  };

  const getNextMonthDays = (year, month) => {
    const nextMonth = month === 11 ? 0 : month + 1;
    const nextMonthYear = month === 11 ? year + 1 : year;
    const nextMonthDays = getDaysInMonth(nextMonthYear, nextMonth);
    const nextMonthFirstDay = getFirstDayOfMonth(nextMonthYear, nextMonth);
    return { nextMonthDays, nextMonthFirstDay };
  };

  const daysInMonth = getDaysInMonth(year, month);
  const firstDayOfMonth = getFirstDayOfMonth(year, month);

  const { prevMonthDays, prevMonthFirstDay } = getPrevMonthDays(year, month);
  const { nextMonthDays, nextMonthFirstDay } = getNextMonthDays(year, month);

  const prevRemainDays = firstDayOfMonth;

  const days = [];
  let l = 0;
  if (firstDayOfMonth !== 6) {
    for (let i = prevMonthDays - firstDayOfMonth; i < prevMonthDays + 1; i++) {
      l += 1;
      // 日付の形式を整える
      const date = i;
      const targetmonth = "prevMonth";
      const formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(
        date
      ).padStart(2, "0")}`;
      // その日が祝日かどうかを確認
      const isHoliday = holidays[formattedDate] || null;

      // 配列にオブジェクトを追加
      days.push({
        day: date, // 日付
        isHoliday: !!isHoliday, // 祝日かどうか (真偽値)
        holidayName: isHoliday || null, // 祝日名または種別
        targetmonth: targetmonth, // 月の分類 (prevMonth, nowMonth, nextMonth)
        formattedDate: formattedDate, // 日付の文字列形式
      });
    }
  }

  for (let i = 1; i <= daysInMonth; i++) {
    const date = i;
    const targetmonth = "nowMonth";
    const formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(
      date
    ).padStart(2, "0")}`;
    const formattedDateForHoliday = `${year + Math.floor(month / 12)}-${String(
      (month % 12) + 1
    ).padStart(2, "0")}-${String(date).padStart(2, "0")}`;
    // その日が祝日かどうかを確認
    const isHoliday = holidays[formattedDateForHoliday] || null;

    // 配列にオブジェクトを追加
    days.push({
      day: date, // 日付
      isHoliday: !!isHoliday, // 祝日かどうか (真偽値)
      holidayName: isHoliday || null, // 祝日名または種別
      targetmonth: targetmonth, // 月の分類 (prevMonth, nowMonth, nextMonth)
      formattedDate: formattedDate, // 日付の文字列形式
    });
  }

  for (let i = 1; i <= 6 - nextMonthFirstDay; i++) {
    let nextmonth = 0;
    let nextmonthyear = 0;
    if (daysInMonth + l === 35 || daysInMonth + l === 28) {
      let l = null;
    } else {
      const date = i;
      const targetmonth = "nextMonth";
      if (month === 11) {
        nextmonth = 0;
        nextmonthyear = year + 1;
      } else {
        nextmonth = month + 2;
        nextmonthyear = year;
      }
      const formattedDate = `${nextmonthyear}-${String(nextmonth).padStart(
        2,
        "0"
      )}-${String(date).padStart(2, "0")}`;
      console.log("月だよ" + nextmonth);
      // その日が祝日かどうかを確認
      const isHoliday = holidays[formattedDate] || null;

      // 配列にオブジェクトを追加
      days.push({
        day: date, // 日付
        isHoliday: !!isHoliday, // 祝日かどうか (真偽値)
        holidayName: isHoliday || null, // 祝日名または種別
        targetmonth: targetmonth, // 月の分類 (prevMonth, nowMonth, nextMonth)
        formattedDate: formattedDate, // 日付の文字列形式
      });
    }
  }

  const handleYearChange = (direction) => {
    setYear((prev) => prev + direction);
  };

  const handleMonthChange = (direction) => {
    setMonth((prev) => {
      const newMonth = prev + direction;
      if (newMonth < 0) {
        setYear((y) => y - 1);
        return 11;
      } else if (newMonth > 11) {
        setYear((y) => y + 1);
        return 0;
      }
      return newMonth;
    });
  };

  useEffect(() => {
    const fetchHolidays = async (year) => {
      const response = await fetch(
        `https://holidays-jp.github.io/api/v1/date.json`
      );
      if (response.ok) {
        const data = await response.json();
        const filteredHolidays = {};
        for (const [date, name] of Object.entries(data)) {
          if (date.startsWith(year) || date.startsWith(year + 1)) {
            filteredHolidays[date] = name;
          }
        }
        setHolidays(filteredHolidays);
      } else {
        console.error("祝日データの取得に失敗しました。");
      }
    };
    fetchHolidays(year);
  }, [year]);
  const openModal = () => {
    setShowModal(true);
  };

  const closeModal = () => {
    setShowModal(false);
  };

  const years = [];
  const yearSelect = () => {
    for (let year = 2100; year >= 1924; year--) {
      years.push(year);
    }
  };
  yearSelect();

  const handleChange = (event) => {
    setSelectedYear(event.target.value);
  };

  const changeSelectYear = () => {
    setYear(selectedYear);
    setShowModal(false);
  };

  const changeSelectDay = (day) => {
    if (day.targetmonth === "prevMonth") {
      let premonth = "";
      let preyear = "";
      if (month === 0) {
        premonth = 12;
        preyear = year - 1;
      } else {
        premonth = month;
        preyear = year;
      }
      const newSelectDay = `${preyear}-${String(premonth).padStart(
        2,
        "0"
      )}-${String(day.day).padStart(2, "0")}`;
      setSelectDay(newSelectDay);
    } else if (day.targetmonth === "nextMonth") {
      let nextmonth = "";
      let nextyear = "";
      if (month === 11) {
        nextmonth = 1;
        nextyear = year + 1;
      } else {
        nextmonth = month + 2;
        nextyear = year;
      }
      const newSelectDay = `${nextyear}-${String(nextmonth).padStart(
        2,
        "0"
      )}-${String(day.day).padStart(2, "0")}`;
      setSelectDay(newSelectDay);
    } else {
      const newSelectDay = `${year}-${String(month + 1).padStart(
        2,
        "0"
      )}-${String(day.day).padStart(2, "0")}`;
      setSelectDay(newSelectDay);
    }
  };

  // selectDayが変更された時にアラートを表示
  useEffect(() => {
    if (selectDay) {
      alert(selectDay);
    }
  }, [selectDay]);

  return (
    <>
      <div className="calendar-container">
        <div className="calendar-content">
          <button id="year_button" onClick={openModal}>
            {year} / {month + 1 > 12 ? 1 : month + 1}
          </button>
          <button
            className="calendar-year-prev"
            onClick={() => handleYearChange(-1)}
          >
            ▲
          </button>
          <button
            className="calendar-year-next"
            onClick={() => handleYearChange(1)}
          >
            ▼
          </button>
          <button
            className="calendar-prev"
            onClick={() => handleMonthChange(-1)}
          >
            ◀◀
          </button>
          <button
            className="calendar-next"
            onClick={() => handleMonthChange(1)}
          >
            ▶▶
          </button>
          <div className="calendar-field">
            {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(
              (day, index) => (
                <div
                  className={`calendar-label ${
                    index === 0 ? "sunday" : index === 6 ? "saturday" : ""
                  }`}
                  key={day}
                >
                  {day}
                </div>
              )
            )}
            {days.map((day, index) => {
              const dayOfWeek = index % 7;
              const dateKey = `${year}-${String(month + 1).padStart(
                2,
                "0"
              )}-${String(day.date).padStart(2, "0")}`;
              return (
                <div
                  className={`calendar-cell ${
                    day.day && dayOfWeek === 0
                      ? "sunday"
                      : day.day && dayOfWeek === 6
                      ? "saturday"
                      : "weekday"
                  } ${day.isHoliday ? "holiday" : ""} ${
                    day.targetmonth === "prevMonth" ? "prevmonth" : ""
                  } ${day.targetmonth === "nextMonth" ? "nextmonth" : ""}`}
                  key={index}
                  onClick={() => day.day && changeSelectDay(day)}
                >
                  {day.day || ""}
                  <div className="holiday-name">{day.holidayName || ""}</div>
                </div>
              );
            })}
          </div>
          <div id="modal" style={{ display: showModal ? "" : "none" }}>
            <div className="modal-container">
              <select
                id="year-select"
                value={selectedYear}
                onChange={handleChange}
              >
                {years.map((year) => (
                  <option key={year} value={year}>
                    {year}
                  </option>
                ))}
              </select>
              <button id="modal-ok" onClick={changeSelectYear}>
                OK
              </button>
              <button id="modal-cancel" onClick={closeModal}>
                Cancel
              </button>
            </div>
          </div>
          <div className="selected-day">
            {selectDay && <p>選択した日: {selectDay}</p>}
          </div>
        </div>
      </div>
    </>
  );
};

export default Calendar;

 ```
##  カレンダーSCSS
 ```:calendar.scss
/* 全体のコンテナ */
.calendar-container {
  width: 100%;
}

/* カレンダーの内容 */
.calendar-content {
  position: relative;
  width: 700px;
  height: 660px;
  padding: 10px;
  font-size: 18px;
  font-style: italic;
  margin: 10px auto;
  justify-content: center;
  text-align: center;
  @media (max-width: 576px) {
    margin: 5px auto;
    width: 98%;
    height: 400px;
  }
}

/* 年変更ボタン */
#year_button {
  position: absolute;
  top: 28px;
  left: 12px;
  font-size: 30px;
  border: none;
  padding: 1px 15px 0;
  border-radius: 5px;
  background-color: #0074d0;
  color: white;
  font-style: italic;
  box-shadow: 3px 3px 3px -3px black;
  transition: background-color 0.3s ease, color 0.3s ease;
  @media (max-width: 576px) {
    top: 12px;
    left: 18px;
    font-size: 20px;
  }
}
#year_button:hover {
  background-color: #6df049;
}

/* 年移動ボタン */
.calendar-year-prev,
.calendar-year-next {
  border: none;
  font-size: 12px;
  font-style: italic;
  position: absolute;
  transform: scale(5, 1);
  color: #0074d0;
  background-color: transparent;
  @media (max-width: 576px) {
    font-size: 10px;
  }
}
.calendar-year-prev {
  top: 15px;
  left: 82px;
  @media (max-width: 576px) {
    top: 0;
    left: 65px;
  }
}
.calendar-year-next {
  top: 64px;
  left: 82px;
  @media (max-width: 576px) {
    top: 36.6px;
    left: 65px;
  }
}
.calendar-year-prev:hover,
.calendar-year-next:hover {
  color: #6df049;
}

/* 月移動ボタン */
.calendar-prev,
.calendar-next {
  font-size: 20px;
  font-style: italic;
  position: absolute;
  color: #0074d0;
  background-color: transparent;
  border: none;
  top: 54px;
  @media (max-width: 576px) {
    font-size: 14px;
    top: 28px;
  }
}
.calendar-prev {
  right: 62px;
  @media (max-width: 576px) {
    right: 44px;
  }
}
.calendar-next {
  right: 10px;
}
.calendar-prev:hover,
.calendar-next:hover {
  color: #6df049;
}

/* カレンダーのフィールド */
.calendar-field {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin: 80px auto !important;
  width: 100%;
  border-bottom: 3px solid #0074d0;
  border-left: 3px solid #0074d0;
  @media (max-width: 576px) {
    margin: 44px auto !important;
  }
}

/* 曜日ラベル */
.calendar-label {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50px !important;
  color: white;
  text-align: center;
  border-right: 3px solid white;
  border-top: 3px solid #0074d0;
  margin: 0 !important;
  font-size: 22px;
  font-weight: 400;
  background-color: #0074d0;
  @media (max-width: 576px) {
    font-size: 14px;
  }
}
.calendar-label.sunday {
  color: red;
}
.calendar-label.saturday {
  color: blue;
  border-right: 3px solid #0074d0;
}

/* カレンダーのセル */
.calendar-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  height: 59px;
  background-color: white;
  border-right: 3px solid #0074d0;
  border-top: 3px solid #0074d0;
  margin: 0;
  @media (max-width: 576px) {
    font-size: 18px;
    height: 54px;
  }
}
.calendar-cell.sunday {
  color: red;
}
.calendar-cell.sunday.prevmonth,
.calendar-cell.sunday.nextmonth {
  color: rgb(200, 36, 36);
  font-size: 14px;
  background-color: rgb(228, 231, 230);
}
.calendar-cell.saturday {
  color: blue;
}
.calendar-cell.saturday.prevmonth,
.calendar-cell.saturday.nextmonth {
  color: rgb(29, 29, 208);
  font-size: 14px;
  background-color: rgb(228, 231, 230);
}
.calendar-cell.weekday {
  color: black;
}
.calendar-cell.weekday.prevmonth,
.calendar-cell.weekday.nextmonth {
  color: rgb(108, 105, 105);
  font-size: 14px;
  background-color: rgb(228, 231, 230);
}
.calendar-cell.weekday.holiday,
.calendar-cell.saturday.holiday {
  color: red;
  position: relative;
}

/* 祝日名 */
div.holiday-name {
  position: absolute;
  font-size: 8px !important;
  color: red;
  text-align: center;
  top: 40px;
  @media (max-width: 576px) {
    font-size: 6px !important;
    top: 40px;
  }
}

/* モーダルスタイル */
#modal {
  position: absolute;
  width: 250px;
  height: 59px;
  margin-top: 0;
  font-size: 24px;
  background-color: white;
  border: solid 4px;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 16px;
  z-index: 2;
}
.modal-container {
  display: flex;
  padding: 0 !important;
}

/* モーダルのボタンとセレクトボックス */
select#year-select {
  font-size: 21px;
  font-style: italic;
  margin: 0 5px;
  padding: 0 10px;
  border: none;
  background-color: transparent;
}
button#modal-ok,
button#modal-cancel {
  border: none;
  background-color: #0074d0;
  color: white;
  font-size: 19px;
  margin: 0 5px;
  padding: 1px 7px 1px 3px !important;
  font-style: italic;
  border-radius: 5px;
}
button#modal-cancel {
  background-color: red;
}
 ```
 
## 書き込み等ができるコード
　上記を進化させて、カレンダーにイベントを追加してデータベースとの連携の元となるものを作成しました。

 ## カレンダーファイル
 ```:calendar.js
 import React, { cloneElement, useEffect, useState } from "react";
import "./Calendar.scss";

const Calendar = () => {
  const [year, setYear] = useState(new Date().getFullYear());
  const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
  const [month, setMonth] = useState(new Date().getMonth());
  const [holidays, setHolidays] = useState({});
  const [showModal, setShowModal] = useState(false);
  const [events, setEvents] = useState({});
  const [selectDay, setSelectDay] = useState("");
  const [newEventTitle, setNewEventTitle] = useState("");
  const [newEventContent, setNewEventContent] = useState("");

  const getDaysInMonth = (year, month) => {
    return new Date(year, month + 1, 0).getDate();
  };

  const getFirstDayOfMonth = (year, month) => {
    return new Date(year, month, 1).getDay();
  };

  const getPrevMonthDays = (year, month) => {
    const prevMonth = month === 0 ? 11 : month - 1;
    const prevMonthYear = month === 0 ? year - 1 : year;
    const prevMonthDays = getDaysInMonth(prevMonthYear, prevMonth);
    const prevMonthFirstDay = getFirstDayOfMonth(prevMonthYear, prevMonth);
    return { prevMonthDays, prevMonthFirstDay };
  };

  const getNextMonthDays = (year, month) => {
    const nextMonth = month === 11 ? 0 : month + 1;
    const nextMonthYear = month === 11 ? year + 1 : year;
    const nextMonthDays = getDaysInMonth(nextMonthYear, nextMonth);
    const nextMonthFirstDay = getFirstDayOfMonth(nextMonthYear, nextMonth);
    return { nextMonthDays, nextMonthFirstDay };
  };

  const daysInMonth = getDaysInMonth(year, month);
  const firstDayOfMonth = getFirstDayOfMonth(year, month);

  const { prevMonthDays, prevMonthFirstDay } = getPrevMonthDays(year, month);
  const { nextMonthDays, nextMonthFirstDay } = getNextMonthDays(year, month);

  const prevRemainDays = firstDayOfMonth;

  const renderDays = () => {
    const days = [];
    let l = 0;
    if (firstDayOfMonth !== 6) {
      for (
        let i = prevMonthDays - firstDayOfMonth;
        i < prevMonthDays + 1;
        i++
      ) {
        l += 1;
        // 日付の形式を整える
        const date = i;
        const targetmonth = "prevMonth";
        let prevmonth = "";
        let prevyear = "";
        if (month === 0) {
          prevmonth = 12;
          prevyear = year - 1;
        } else {
          prevmonth = month;
          prevyear = year;
        }
        const formattedDate = `${prevyear}-${String(prevmonth).padStart(
          2,
          "0"
        )}-${String(date).padStart(2, "0")}`;
        const isHoliday = holidays[formattedDate] || null;
        // const eventall = events[formattedDate];
        days.push({
          day: date,
          isHoliday: !!isHoliday,
          holidayName: isHoliday || null,
          targetmonth: targetmonth,
          formattedDate: formattedDate,
          // event: eventall,
        });
      }
    }

    for (let i = 1; i <= daysInMonth; i++) {
      const date = i;
      const targetmonth = "nowMonth";
      const formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(
        date
      ).padStart(2, "0")}`;
      const formattedDatekai = `${year}-${String(month + 1).padStart(
        2,
        "0"
      )}-${String(date).padStart(2, "0")}`;
      const formattedDateForHoliday = `${
        year + Math.floor(month / 12)
      }-${String((month % 12) + 1).padStart(2, "0")}-${String(date).padStart(
        2,
        "0"
      )}`;

      const isHoliday = holidays[formattedDateForHoliday] || null;
      const eventall = events[formattedDatekai];

      days.push({
        day: date,
        isHoliday: !!isHoliday,
        holidayName: isHoliday || null,
        targetmonth: targetmonth,
        formattedDate: formattedDatekai,
        event: eventall || [],
      });
    }

    for (let i = 1; i <= 6 - nextMonthFirstDay; i++) {
      let nextmonth = 0;
      let nextmonthyear = 0;
      if (daysInMonth + l === 35 || daysInMonth + l === 28) {
        let l = null;
      } else {
        const date = i;
        const targetmonth = "nextMonth";
        if (month === 11) {
          nextmonth = 1;
          nextmonthyear = year + 1;
        } else {
          nextmonth = month + 2;
          nextmonthyear = year;
        }
        const formattedDate = `${nextmonthyear}-${String(nextmonth).padStart(
          2,
          "0"
        )}-${String(date).padStart(2, "0")}`;

        const isHoliday = holidays[formattedDate] || null;
        // const eventall = events[formattedDate];
        days.push({
          day: date,
          isHoliday: !!isHoliday,
          holidayName: isHoliday || null,
          targetmonth: targetmonth,
          formattedDate: formattedDate,
          // event: eventall,
        });
      }
    }

    return days;
  };

  const handleYearChange = (direction) => {
    setYear((prev) => prev + direction);
  };

  const handleMonthChange = (direction) => {
    setMonth((prev) => {
      const newMonth = prev + direction;
      if (newMonth < 0) {
        setYear((y) => y - 1);
        return 11;
      } else if (newMonth > 11) {
        setYear((y) => y + 1);
        return 0;
      }
      return newMonth;
    });
  };

  useEffect(() => {
    const fetchHolidays = async (year) => {
      const response = await fetch(
        `https://holidays-jp.github.io/api/v1/date.json`
      );
      if (response.ok) {
        const data = await response.json();
        const filteredHolidays = {};
        for (const [date, name] of Object.entries(data)) {
          if (date.startsWith(year) || date.startsWith(year + 1)) {
            filteredHolidays[date] = name;
          }
        }
        setHolidays(filteredHolidays);
      } else {
        console.error("祝日データの取得に失敗しました。");
      }
    };
    fetchHolidays(year);
  }, [year]);
  const openModal = () => {
    setShowModal(true);
  };

  const closeModal = () => {
    setShowModal(false);
  };

  const years = [];
  const yearSelect = () => {
    for (let year = 2100; year >= 1924; year--) {
      years.push(year);
    }
  };
  yearSelect();

  const handleChange = (event) => {
    setSelectedYear(event.target.value);
  };

  const changeSelectYear = () => {
    setYear(selectedYear);
    setShowModal(false);
  };

  const changeSelectDay = (day) => {
    if (day.targetmonth === "prevMonth") {
      let premonth = "";
      let preyear = "";
      if (month === 0) {
        premonth = 12;
        preyear = year - 1;
      } else {
        premonth = month;
        preyear = year;
      }
      const newSelectDay = `${preyear}-${String(premonth).padStart(
        2,
        "0"
      )}-${String(day.day).padStart(2, "0")}`;
      setSelectDay(newSelectDay);
    } else if (day.targetmonth === "nextMonth") {
      let nextmonth = "";
      let nextyear = "";
      if (month === 11) {
        nextmonth = 1;
        nextyear = year + 1;
      } else {
        nextmonth = month + 2;
        nextyear = year;
      }
      const newSelectDay = `${nextyear}-${String(nextmonth).padStart(
        2,
        "0"
      )}-${String(day.day).padStart(2, "0")}`;
      setSelectDay(newSelectDay);
    } else {
      const newSelectDay = `${year}-${String(month + 1).padStart(
        2,
        "0"
      )}-${String(day.day).padStart(2, "0")}`;
      setSelectDay(newSelectDay);
    }
  };

  // selectDayが変更された時にアラートを表示
  useEffect(() => {
    if (selectDay) {
      // alert(selectDay);
    }
  }, [selectDay]);

  const handleAddEvent = () => {
    console.log("handleAddEvent executed");

    if (newEventTitle.trim().length > 10) {
      alert("タイトルは4文字以内にしてください。");
      return;
    }
    const eventData = {
      title: newEventTitle, // eventTitle
      content: newEventContent, // eventContent
    };

    if (!newEventTitle.trim()) return; // 空白入力を無視
    setEvents((prevEvents) => {
      const updatedEvents = { ...prevEvents };
      if (!updatedEvents[selectDay]) {
        updatedEvents[selectDay] = [];
      }
      // 重複を防ぐ処理を追加
      if (
        !updatedEvents[selectDay].some(
          (event) =>
            event.title === eventData.title &&
            event.content === eventData.content
        )
      ) {
        updatedEvents[selectDay].push(eventData);
      }
      return updatedEvents;
    });
    setNewEventTitle("");
    setNewEventContent("");
    setSelectDay(null);
  };

  const handleEventClick = (event) => {
    alert(`"イベント詳細をリンクしましょう" + ${event.title}`);
  };
  return (
    <>
      <div className="calendar-container">
        <div className="calendar-content">
          <button id="year_button" onClick={openModal}>
            {year} / {month + 1 > 12 ? 1 : month + 1}
          </button>
          <button
            className="calendar-year-prev"
            onClick={() => handleYearChange(-1)}
          >
            ▲
          </button>
          <button
            className="calendar-year-next"
            onClick={() => handleYearChange(1)}
          >
            ▼
          </button>
          <button
            className="calendar-prev"
            onClick={() => handleMonthChange(-1)}
          >
            ◀◀
          </button>
          <button
            className="calendar-next"
            onClick={() => handleMonthChange(1)}
          >
            ▶▶
          </button>
          <div className="calendar-field">
            {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(
              (day, index) => (
                <div
                  className={`calendar-label ${
                    index === 0 ? "sunday" : index === 6 ? "saturday" : ""
                  }`}
                  key={day}
                >
                  {day}
                </div>
              )
            )}
            {renderDays().map((day, index) => {
              const dayOfWeek = index % 7;
              const dateKey = `${year}-${String(month + 1).padStart(
                2,
                "0"
              )}-${String(day.date).padStart(2, "0")}`;
              return (
                <div className="calendar-cell">
                  <div
                    className={`calendar-cell-inner ${
                      day.day && dayOfWeek === 0
                        ? "sunday"
                        : day.day && dayOfWeek === 6
                        ? "saturday"
                        : "weekday"
                    } ${day.isHoliday ? "holiday" : ""} ${
                      day.targetmonth === "prevMonth" ? "prevmonth" : ""
                    } ${day.targetmonth === "nextMonth" ? "nextmonth" : ""}`}
                    key={index}
                    onClick={() => day.day && changeSelectDay(day)}
                  >
                    {day.day || ""}{" "}
                  </div>
                  <div className="holiday-name">{day.holidayName || ""}</div>
                  <div className="calendar-events">
                    {day.event &&
                      day.event.map((event, index) => (
                        <div className="calendar-event" key={index}>
                          <p
                            className="calendar-event-button"
                            onClick={() => handleEventClick(event)}
                          >
                            {event.title}
                          </p>
                          {/* <p>{event.title}</p> */}
                          {/* <p>{event.content}</p> */}
                        </div>
                      ))}
                  </div>
                </div>
              );
            })}
          </div>
          <div>ぞんおう{JSON.stringify(events)}</div>

          {selectDay && (
            <div className="calendar-modal-input-field">
              <h3>{`${selectDay}の予定`}</h3>
              <input
                type="text"
                value={newEventTitle}
                onChange={(e) => setNewEventTitle(e.target.value)}
                placeholder="タイトル入力（カレンダー表示は3文字のみ）"
              />
              <textarea
                type="text"
                value={newEventContent}
                onChange={(e) => setNewEventContent(e.target.value)}
                placeholder="予定を入力"
              />
              <div className="calendar-modal-input-button">
                <button onClick={handleAddEvent}>追加</button>
                <button onClick={() => setSelectDay(null)}>キャンセル</button>
              </div>
            </div>
          )}

          <div id="modal" style={{ display: showModal ? "" : "none" }}>
            <div className="modal-container">
              <select
                id="year-select"
                value={selectedYear}
                onChange={handleChange}
              >
                {years.map((year) => (
                  <option key={year} value={year}>
                    {year}
                  </option>
                ))}
              </select>
              <button id="modal-ok" onClick={changeSelectYear}>
                OK
              </button>
              <button id="modal-cancel" onClick={closeModal}>
                Cancel
              </button>
            </div>
          </div>
          <div className="selected-day">
            {selectDay && <p>選択した日: {selectDay}</p>}
          </div>
        </div>
      </div>
    </>
  );
};
export default Calendar;

 ```
 ## カレンダーSCSS
 ```:calendar.scss
 /* 全体のコンテナ */
.calendar-container {
  width: 100%;
}

/* カレンダーの内容 */
.calendar-content {
  position: relative;
  width: 700px;
  height: 660px;
  padding: 10px;
  font-size: 18px;
  font-style: italic;
  margin: 10px auto;
  justify-content: center;
  text-align: center;
  @media (max-width: 576px) {
    margin: 5px auto;
    width: 98%;
    height: 400px;
  }
}

/* 年変更ボタン */
#year_button {
  position: absolute;
  top: 28px;
  left: 12px;
  font-size: 30px;
  border: none;
  padding: 1px 15px 0;
  border-radius: 5px;
  background-color: #0074d0;
  color: white;
  font-style: italic;
  box-shadow: 3px 3px 3px -3px black;
  transition: background-color 0.3s ease, color 0.3s ease;
  @media (max-width: 576px) {
    top: 12px;
    left: 18px;
    font-size: 20px;
  }
}
#year_button:hover {
  background-color: #6df049;
}

/* 年移動ボタン */
.calendar-year-prev,
.calendar-year-next {
  border: none;
  font-size: 12px;
  font-style: italic;
  position: absolute;
  transform: scale(5, 1);
  color: #0074d0;
  background-color: transparent;
  @media (max-width: 576px) {
    font-size: 10px;
  }
}
.calendar-year-prev {
  top: 15px;
  left: 82px;
  @media (max-width: 576px) {
    top: 0;
    left: 65px;
  }
}
.calendar-year-next {
  top: 64px;
  left: 82px;
  @media (max-width: 576px) {
    top: 36.6px;
    left: 65px;
  }
}
.calendar-year-prev:hover,
.calendar-year-next:hover {
  color: #6df049;
}

/* 月移動ボタン */
.calendar-prev,
.calendar-next {
  font-size: 20px;
  font-style: italic;
  position: absolute;
  color: #0074d0;
  background-color: transparent;
  border: none;
  top: 54px;
  @media (max-width: 576px) {
    font-size: 14px;
    top: 28px;
  }
}
.calendar-prev {
  right: 62px;
  @media (max-width: 576px) {
    right: 44px;
  }
}
.calendar-next {
  right: 10px;
}
.calendar-prev:hover,
.calendar-next:hover {
  color: #6df049;
}

/* カレンダーのフィールド */
.calendar-field {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin: 80px auto !important;
  width: 100%;
  border-bottom: 3px solid #0074d0;
  border-left: 3px solid #0074d0;
  @media (max-width: 576px) {
    margin: 44px auto !important;
  }
}

/* 曜日ラベル */
.calendar-label {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 50px !important;
  color: white;
  text-align: center;
  border-right: 3px solid white;
  border-top: 3px solid #0074d0;
  margin: 0 !important;
  font-size: 22px;
  font-weight: 400;
  background-color: #0074d0;
  @media (max-width: 576px) {
    font-size: 14px;
  }
}
.calendar-label.sunday {
  color: red;
}
.calendar-label.saturday {
  color: blue;
  border-right: 3px solid #0074d0;
}

.calendar-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  height: 74px;
  background-color: white;
  border-right: 3px solid #0074d0;
  border-top: 3px solid #0074d0;
  margin: 0;
  @media (max-width: 576px) {
    font-size: 18px;
    height: 54px;
  }
}

/* カレンダーのセル */
.calendar-cell-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  height: 74px;
  background-color: white;

  margin: 0;
  @media (max-width: 576px) {
    font-size: 18px;
    height: 54px;
  }
}
.calendar-cell-inner.sunday {
  color: red;
}
.calendar-cell-inner.sunday.prevmonth,
.calendar-cell-inner.sunday.nextmonth {
  color: rgb(200, 36, 36);
  width: 100%;
  font-size: 14px;
  background-color: rgb(228, 231, 230);
}
.calendar-cell-inner.saturday {
  color: blue;
}
.calendar-cell-inner.saturday.prevmonth,
.calendar-cell-inner.saturday.nextmonth {
  width: 100%;
  color: rgb(29, 29, 208);
  font-size: 14px;
  background-color: rgb(228, 231, 230);
}
.calendar-cell-inner.weekday {
  color: black;
}
.calendar-cell-inner.weekday.prevmonth,
.calendar-cell-inner.weekday.nextmonth {
  width: 100%;
  color: rgb(108, 105, 105);
  font-size: 14px;
  background-color: rgb(228, 231, 230);
}
.calendar-cell-inner.weekday.holiday,
.calendar-cell-inner.saturday.holiday {
  width: 100%;
  color: red;
  position: relative;
}

/* 祝日名 */
div.holiday-name {
  position: absolute;
  font-size: 8px !important;
  color: red;
  text-align: center;
  top: 55px;
  @media (max-width: 576px) {
    font-size: 6px !important;
    top: 40px;
  }
}

/* モーダルスタイル */
#modal {
  position: absolute;
  width: 250px;
  height: 59px;
  margin-top: 0;
  font-size: 24px;
  background-color: white;
  border: solid 4px;
  border-radius: 10px;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 16px;
  z-index: 2;
}
.modal-container {
  display: flex;
  padding: 0 !important;
}

/* モーダルのボタンとセレクトボックス */
select#year-select {
  font-size: 21px;
  font-style: italic;
  margin: 0 5px;
  padding: 0 10px;
  border: none;
  background-color: transparent;
}
button#modal-ok,
button#modal-cancel {
  border: none;
  background-color: #0074d0;
  color: white;
  font-size: 19px;
  margin: 0 5px;
  padding: 1px 7px 1px 3px !important;
  font-style: italic;
  border-radius: 5px;
}
button#modal-cancel {
  background-color: red;
}

.calendar-modal-input-field {
  background-color: #25c732;
  position: absolute;
  padding: 0 10px;
  top: 150px;
  left: 200px;
  width: 350px;
  height: 270px;
  border-radius: 5px;
  box-shadow: 3px 3px 3px -3px black;
  @media (max-width: 576px) {
    top: 50px;
    left: 10px;
    width: 95%;
    height: 250px;
  }
  h3 {
    padding: 5px !important;
    margin: 5px 0 0;
    color: white;
    @media (max-width: 576px) {
      font-size: 16px;
    }
  }
  input {
    padding: 5px;
    margin: 0 0 20px;
    width: 95%;
    border-radius: 3px;
    border: none;
    @media (max-width: 576px) {
      font-size: 14px;
    }
  }

  textarea {
    margin: 0;
    width: 95%;
    height: 100px;
    border-radius: 3px;
    border: none;
    @media (max-width: 576px) {
      font-size: 14px;
    }
  }
}

.calendar-modal-input-button {
  margin: 0 20px;
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 10px;
}
.calendar-modal-input-button button {
  background-color: white;
  color: #25c732;
  font-size: 16px;
  display: flex;
  justify-content: space-between;
  gap: 30px;
  border: none;
  margin: 10px 10px;
  padding: 5px 10px;
  border-radius: 5px;
  box-shadow: 5px 5px 5px -5px black;
  @media (max-width: 576px) {
    font-size: 14px;
    margin: 0 10px;
    padding: 0 5px;
  }
}

.calendar-event {
  background-color: rgba(48, 123, 221, 0.5);
  font-size: 10px;
  font-style: italic;
  border-radius: 2px;
  width: 30px;
  height: 16px;
  position: absolute;
  bottom: 0;
  border-top: white solid 2px;
  left: 63px;
  text-align: left;
  transform: skew(-15deg);
  overflow: hidden;
  white-space: nowrap;
  @media (max-width: 576px) {
    height: 10px;
    width: 18px;
  }
  p {
    font-size: 10px;
    @media (max-width: 576px) {
      font-size: 6px;
    }
  }
}

.calendar-event:nth-child(1) {
  position: absolute;
  top: 0;
  left: 1px;
}
.calendar-event:nth-child(2) {
  top: 14px;
  left: 1px;
  @media (max-width: 576px) {
    top: 9px;
  }
}

.calendar-event:nth-child(3) {
  top: 28px;
  left: 1px;
  @media (max-width: 576px) {
    top: 18px;
  }
}
.calendar-event:nth-child(4) {
  top: 42px;
  left: 1px;
  @media (max-width: 576px) {
    top: 27px;
  }
}
.calendar-event:nth-child(5) {
  top: 56px;
  left: 1px;
  @media (max-width: 576px) {
    top: 36px;
  }
}
.calendar-event:nth-child(6) {
  top: 0;
  left: 62px;
  @media (max-width: 576px) {
    top: 0;
    left: 48px;
  }
}
.calendar-event:nth-child(7) {
  top: 14px;
  left: 62px;
  @media (max-width: 576px) {
    top: 9px;
    left: 48px;
  }
}

.calendar-event:nth-child(8) {
  top: 28px;
  left: 62px;
  @media (max-width: 576px) {
    top: 18px;
    left: 48px;
  }
}

.calendar-event:nth-child(9) {
  top: 42px;
  @media (max-width: 576px) {
    top: 27px;
    left: 48px;
  }
}

.calendar-event:nth-child(10) {
  top: 56px;
  @media (max-width: 576px) {
    top: 36px;
    left: 48px;
  }
}

 ```