: JavaScript๋ก ๋ง๋ค์ด์ง ๋ฌ๋ ฅ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก์จ month, week, day ๋ณ๋ก ๋๋์ด ์ผ์ ์ ๊ด๋ฆฌํ ์ ์๋ค.
1๏ธโฃ ๋ฌ๋ ฅ ๋์ฐ๊ธฐ
2๏ธโฃ ์ด๋ฒคํธ ์ ๋ฌด์ ๋ฐ๋ผ ์์ฑ/์กฐํํ์ด์ง๋ก ์ด๋
3๏ธโฃ ์ด๋ฒคํธ ์ ๋ฌด์ ๋ฐ๋ผ hover ์ [+] ๋ฒํผ ๋
ธ์ถ
npm install @fullcalendar/react @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction
MyCalendar.jsx ์ปดํฌ๋ํธ ํ์ผ์ ๋ง๋ค์ด์ app.jsx์์ ์ ์ธํด์ ๋ถ๋ฌ์์ด๋ค
๋ฌ๋ ฅ์๋ ์ผ์ ์ด ์์ด์ผํ๋ ๋๋ฏธ ๊ฐ์ ์
๋ ฅํด์คฌ์ด๋ค
const events = [
{ title: '๐ก', date: '2024-12-17', },
{ title: '๐', date: '2024-12-19', },
{ title: '๐ณ', date: '2024-12-23', },
];
<FullCalendar
defaultView="dayGridMonth"
plugins={[ dayGridPlugin, interactionPlugin]}
events={events}
dateClick={(info) => {
alert(`๋ ์ง ํด๋ฆญ๋จ: ${info.dateStr}`);
}}
eventClick={(info) => {
alert(`์ด๋ฒคํธ ํด๋ฆญ๋จ: ${info.event.startStr}`);
}}
/>

App.jsx
<Router>
<Routes>
<Route path="/" element={<MyCalendar />} />
<Route path="/diaries/create/:date" element={<CreateDiary />} />
<Route path="/diaries/view/:date" element={<ViewDiary />} />
</Routes>
</Router>
๊ฒฝ๋ก๋ฅผ ์ค์ ํ๊ธฐ ์ํด์๋ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' ์ ์ธ์ด ํ์ํ๋ค
/:date : ์ด๋ํ๊ธฐ์ ํ์ด์ง์์ date ๊ฐ์ ๊ฐ์ ธ์์ ๊ฒฝ๋ก์ ๋ณด์ฌ์ค๋ค.MyCalendar.jsx
// ํ์ด์ง ์ด๋ ํจ์
const navigate = useNavigate();
<FullCalendar
defaultView="dayGridMonth"
plugins={[ dayGridPlugin, interactionPlugin]}
events={events}
dateClick={(info) => {
alert(`๋ ์ง ํด๋ฆญ๋จ: ${info.dateStr}`);
navigate(`/diaries/create/${info.dateStr}`)
}}
eventClick={(info) => {
alert(`์ด๋ฒคํธ ํด๋ฆญ๋จ: ${info.event.startStr}`);
navigate(`/diaries/view/${info.event.startStr}`)
}}
/>
dateClick์ ํ์ ๊ฒฝ์ฐ ์ด๋ํ ์ปดํฌ๋ํธ์ eventClick์ ํ์ ๊ฒฝ์ฐ ์ด๋ํ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ํด์คฌ๋ค.
โก๏ธ ์ฌ๊ธฐ์ App.jsx์ ๊ฒฝ๋ก์ ์๋ /:date์ ${info.dateStr}, ${info.event.startStr} ์ด ๋ค์ด๊ฐ๋ค.

์ด๋ฒคํธ๊ฐ ์๋ ๋ ์ง์ ์๋ถ๋ถ์ ํด๋ฆญํ ๊ฒฝ์ฐ dateClick์ด ๋๋ค โก๏ธ ์ผ๊ธฐ๋ ํ๋ฃจ์ ํ๋ฒ ์์ฑํด์ผํ๋ฏ๋ก dateClick์ด ๋๋ฉด ์๋จ
์ผ์ ์ด ์๋ ๋ ์ง๋ฅผ ๋ถ๋ฌ์์ ๋ด๊ฐ dateClickํ ๋ ์ง์ ๋น๊ตํ์ ๋ ์ด๋ฒคํธ๊ฐ ์์ผ๋ฉด ์กฐํํ์ด์ง๋ฅผ ์์ผ๋ฉด ์์ฑํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ๋๋ค.
const hasEvent = events.some((event) => event.start === info.dateStr);
some ๋ฉ์๋ : ๋ฐฐ์ด์์ ์กฐ๊ฑด์ ๋ง๋ ์์๊ฐ ํ๋๋ผ๋ ์์ผ๋ฉด ture๋ฅผ ๋ฐํํฉ๋๋ค.<FullCalendar
defaultView="dayGridMonth"
plugins={[ dayGridPlugin, interactionPlugin]}
events={events}
dateClick={(info) => {
alert(`๋ ์ง ํด๋ฆญ๋จ: ${info.dateStr}`);
const hasEvent = events.some((event) => event.start === info.dateStr);
if(!hasEvent){
navigate(`/diaries/create/${info.dateStr}`)
}else{
navigate(`/diaries/view/${info.dateStr}`)
}
}}
eventClick={(info) => {
alert(`์ด๋ฒคํธ ํด๋ฆญ๋จ: ${info.event.startStr}`);
navigate(`/diaries/view/${info.event.startStr}`)
}}
/>
fullcalendar๊ฐ ์ข์๋ ์ด์ ๋ ๋ด๊ฐ ์ ํํ ๋ ์ง ๊ฐ์ ์๋์ผ๋ก YYYY-MM-DD ํ์์ผ๋ก ๊ฐ๊ณตํด์ค์ ์ข์์ง๋ง dayCellDidMount ๋ผ๋ ์ฌ์ฉ์ ๋์์ ๋ด๊ฐ ์ปค์คํ
์ ํ ์ ์์ด์ ๋ ์ข์๋ ๊ฒ ๊ฐ๋ค.
: ์ด๋ฒคํธ๊ฐ ์์ผ๋ฉด [+]๋ฒํผ ๋์ค๊ฒ ํ๊ณ ์์ผ๋ฉด ์๋์ค๊ฒํด์ผํ๋๋ฐ ์ผ๋จ ๋ฒํผ๋ถํฐ ๋ง๋ค์๋ค.
const createButton = document.createElement("button");
createButton.textContent = "+";
button์ด๋ผ๋ ํ๊ทธ๊ฐ ์๊ธธ ์ ์๋๋ก ํ๊ณ ์ด ํ๊ทธ ์์ '+' ํ ์คํธ๋ฅผ ๋ฃ์๋ค.
โ๏ธ
dayCellDidMount์๋ ์ฌ์ฉํ ์ ์๋ ์ฃผ์ ์์
- info.date : ๊ฐ๊ณต๋์ง์์ ๋ ์ง ex) Tue Dec 17 2024 00:00:00 GMT+0000
- info.el: ๋ ์ง ์ ์ DOM ์์
dateClick์๋ ๊ฐ๊ณต๋ date๊ฐ ์์ง๋ง dayCellDidMount์ ์๋ date๋ ๊ฐ๊ณต๋์ด์์ง์์ ๋จผ์ YYYY-MM-DD ํ์์ด ๋๋๋ก ๊ฐ๊ณต์ ์์ผ์ค๋ค.
const date = info.date.toLocaleDateString("en-CA");
dayCellDidMount์์ ์๊ฐ์ ์ค์ ํ๋๋ฐ ๋๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
๋๋ค YYYY-MM-DD ํ์์ผ๋ก ๊ฐ๊ณต๋ ์ํ์ด๋ค.
โ๏ธ ์ด์ ์ค์ํ hover ์ค์ ํ๊ธฐ
: dayCellDidMount์์๋ hover๋ฅผ ์ฌ์ฉํ ์ ์๊ณ addEventListener๋ก mouseenter, mouseleave ๋ก ์ฌ์ฉํด์ผํ๋ค.
// ๋ ์ง ์
์ ๋ง์ฐ์ค๊ฐ ์ฌ๋ผ๊ฐ์ ๋
info.el.addEventListener("mouseenter", () => {
info.el.style.backgroundColor = "#ccc"; // ๋ฐฐ๊ฒฝ์ ๋ณ๊ฒฝ
if (!hasEvent){
info.el.appendChild(createButton);
}});
// ๋ ์ง ์
์์ ๋ง์ฐ์ค๊ฐ ๋ ๋ฌ์ ๋
info.el.addEventListener("mouseleave", () => {
info.el.style.backgroundColor = ""; // ์๋ ๋ฐฐ๊ฒฝ์์ผ๋ก ๋ณต๊ตฌ
info.el.removeChild(createButton);
});
โก๏ธ ์ด๋ฒคํธ๊ฐ ์์ ๊ฒฝ์ฐ mouseenter ์ createButton ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๊ณ ์ด๋ฒคํธ๊ฐ ์๋ ์๋ ๋ ์ง ์
์์ ๋ฒ์ด๋ ๊ฒฝ์ฐ mouseleave createButton ์ปดํฌ๋ํธ๋ฅผ ์ง์ด๋ค.
<FullCalendar
defaultView="dayGridMonth"
plugins={[ dayGridPlugin, interactionPlugin]}
events={events}
dateClick={(info) => {
alert(`๋ ์ง ํด๋ฆญ๋จ: ${info.dateStr}`);
const hasEvent = events.some((event) => event.start === info.dateStr);
if(!hasEvent){
navigate(`/diaries/create/${info.dateStr}`)
}else{
navigate(`/diaries/view/${info.dateStr}`)
}
}}
eventClick={(info) => {
alert(`์ด๋ฒคํธ ํด๋ฆญ๋จ: ${info.event.startStr}`);
navigate(`/diaries/view/${info.event.startStr}`)
}}
// ๊ฐ ๋ ์ง๋ณ๋ก ์ด๋ฒคํธ ์ถ๊ฐ ๊ฐ๋ฅํ ๊ธฐ๋ฅ - ๊ฐ ๋ ์ง ์
์ด ๋ ๋๋ง๋ ๋๋ง๋ค ์คํ
// dayCellDidMount ์์ ์ฌ์ฉํ ์ ์๋ ์์
// info.date : ๊ฐ๊ณต๋์ง์์ ๋ ์ง ex) Tue Dec 17 2024 00:00:00 GMT+0000
// info.el: ๋ ์ง ์
์ DOM ์์
dayCellDidMount={(info)=>{
// ๊ฐ๊ณต๋์ง์์ ๋ ์ง ๊ฐ๊ณต์ํค๊ธฐ
const date = info.date.toLocaleDateString("en-CA");
// ๋ ์ง์ ์ด๋ฒคํธ๊ฐ ์๋ ๋ ์ง ๋น๊ต -> ์์ ๊ฒฝ์ฐ True, ์์ ๊ฒฝ์ฐ Fasle
const hasEvent = events.some((event) => event.start === date);
// ๋ ์ง ์
์ ๋ง์ฐ์ค๊ฐ ์ฌ๋ผ๊ฐ์ ๋
info.el.addEventListener("mouseenter", () => {
info.el.style.backgroundColor = "#ccc"; // ๋ฐฐ๊ฒฝ์ ๋ณ๊ฒฝ
if (!hasEvent){
info.el.appendChild(createButton);
}
});
// ๋ ์ง ์
์์ ๋ง์ฐ์ค๊ฐ ๋ ๋ฌ์ ๋
info.el.addEventListener("mouseleave", () => {
info.el.style.backgroundColor = ""; // ์๋ ๋ฐฐ๊ฒฝ์์ผ๋ก ๋ณต๊ตฌ
info.el.removeChild(createButton);
});
}}
/>

Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
at HTMLTableCellElement.<anonymous> (Calendar.jsx:81:29)
์ด๋ฒคํธ ์๋ ๋ ์ง์์ ์ด๋ฒคํธ ์๋ ๋ ์ง๋ก ๋ง์ฐ์ค ์ง๋๊ฐ๋ฉด ์ด ์ค๋ฅ๊ฐ ๊ณ์ ๋จ๋๋ฐ ์ด ์ค๋ฅ๊ฐ ์์ด๋ฉด ์์ข๋ค๊ทธ๋์ ์์ ๊ณ ์ถ์๋ฐ ์์จ ๋ฐฉ๋ฒ์ ๋ชจ๋ฅด๊ฒ ๋ค.. ์ผ๋จ ๊ตฌํํ ๊ฑธ๋ก ๋ง์กฑํ์ง๋ง ์ธ์ ๊ฐ ๊ณ ์น๊ณ ๋ง๋ค ใ กใ ใ ก