Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
754a450
📦[Chore] FullCalendar 라이브러리 설치 #96
cchaeyoung Oct 21, 2024
e7cb349
📦[Chore] Merge develop into feature/calendar-block #96
cchaeyoung Oct 21, 2024
6e4599f
♻️[Refactor] fetchCalendarBlock 함수 리팩토링 #99
cchaeyoung Oct 21, 2024
bd843ac
✨[Feat] 새 일정 추가 기능 구현 #99
cchaeyoung Oct 21, 2024
10c5d5e
✨[Feat] 일정 추가 후 캘린더 페이지로 리다이렉트 #99
cchaeyoung Oct 21, 2024
878225c
♻️[Refactor] 일정 및 캘린더 블록 처리 로직 리팩토링 #99
cchaeyoung Oct 21, 2024
0f9968b
✨[Feat] getSequence 함수 추가 #99
cchaeyoung Oct 21, 2024
64bd9a8
✨[Feat] 일정 조회 API 연동 추가 #99
cchaeyoung Oct 21, 2024
516ee0b
✨[Feat] 일정 삭제 기능 구현 #99
cchaeyoung Oct 21, 2024
7e1c511
♻️[Refactor] 전체 페이지 오류 표시 로직 제거 #96
cchaeyoung Oct 21, 2024
c489125
✨[Feat] 일정 없을 때 빈 상태 메시지 표시 #96
cchaeyoung Oct 21, 2024
48f16d3
🐛[Fix] 일정 페이지 라우팅 경로 수정 #96
cchaeyoung Oct 21, 2024
63ab060
♻️[Refactor] 일정 추가 폼에서 로딩 상태 및 에러 표시 제거 #96
cchaeyoung Oct 21, 2024
0d10263
♻️[Refactor] 파일 구조 변경 #96
cchaeyoung Oct 22, 2024
0602114
♻✨[Feat] 일정 폼 컴포넌트에 수정 기능을 위한 인터페이스 추가 #96
cchaeyoung Oct 22, 2024
6295edd
🎨[Style] 일정 관리 페이지 컴포넌트명 변경 #96
cchaeyoung Oct 22, 2024
2a0b06b
✨[Feat] 일정 수정 기능 구현 #99
cchaeyoung Oct 22, 2024
4483c5d
✨[Feat] FullCalendar 라이브러리를 사용한 캘린더 뷰 구현 #96
cchaeyoung Oct 22, 2024
c847d0d
Merge branch 'develop' into feature/calendar-block
cchaeyoung Oct 22, 2024
18058cf
✨[Feat] hover 시 일정 URL 클릭 #96
cchaeyoung Oct 23, 2024
0a9484f
Merge branch 'develop' into feature/calendar-block
cchaeyoung Oct 23, 2024
2d30e79
Merge branch 'develop' into feature/calendar-block
cchaeyoung Oct 23, 2024
d41520b
✨[Feat] 캘린더 페이지 추가 #96
cchaeyoung Oct 23, 2024
73192f4
✨[Feat] 리스트뷰, 스타일 설정 추가 #96
cchaeyoung Oct 23, 2024
9d09772
♻️[Refactor] 캘린더 경로 수정 (/admin/block/calendar -> /admin/calendar) #96
cchaeyoung Oct 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
},
"dependencies": {
"@fullcalendar/core": "^6.1.15",
"@fullcalendar/daygrid": "^6.1.15",
"@fullcalendar/interaction": "^6.1.15",
"@fullcalendar/react": "^6.1.15",
"moment": "2.30.1",
"next": "14.2.14",
"react": "^18",
Expand Down
36 changes: 0 additions & 36 deletions src/app/admin/(block)/calendar/add/page.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function CalendarHeader() {
const router = useRouter();

const handleAddScheduleClick = () => {
router.push("calendar/add");
router.push("calendar/manage");
};

return (
Expand Down
210 changes: 93 additions & 117 deletions src/app/admin/(block)/calendar/components/calendar-view.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
"use client";

import React, { useState } from "react";
import Image from "next/image";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import { EventContentArg } from "@fullcalendar/core/index.js";

interface Schedule {
id: string;
Expand All @@ -13,40 +19,8 @@ interface CalendarViewProps {
}

const CalendarView: React.FC<CalendarViewProps> = ({ schedules }) => {
const [currentMonth] = useState<Date>(new Date(2023, 0, 1));

const getDaysInMonth = (date: Date): Date[] => {
const year = date.getFullYear();
const month = date.getMonth();
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);

const daysInMonth = [];
const totalDays = 35;

for (let i = 1 - firstDay.getDay(); daysInMonth.length < totalDays; i++) {
daysInMonth.push(new Date(year, month, i));
}

return daysInMonth;
};

const toDate = (date: string | Date): Date => {
return new Date(new Date(date).setHours(0, 0, 0, 0));
};

const calculateOffsets = (start: Date, end: Date, weekStart: Date) => {
const startOffset = Math.max(
0,
(start.getTime() - weekStart.getTime()) / (1000 * 3600 * 24),
);
const endOffset = Math.min(
6,
(end.getTime() - weekStart.getTime()) / (1000 * 3600 * 24),
);
const duration = endOffset - startOffset + 1;
return { startOffset, duration };
};
const [currentMonth, setCurrentMonth] = useState<Date>(new Date(2023, 0, 1));
const calendarRef = React.useRef<FullCalendar>(null);

const getBackgroundColor = (start: Date, end: Date) => {
const startDay = start.getDate();
Expand All @@ -61,97 +35,99 @@ const CalendarView: React.FC<CalendarViewProps> = ({ schedules }) => {
}
};

const renderSchedules = (week: Date[]) => {
const weekStart = week[0];
const weekEnd = week[6];
return schedules
.filter((schedule) => {
const start = toDate(schedule.startDate);
const end = toDate(schedule.endDate);
return start <= weekEnd && end >= weekStart;
})
.map((schedule, index) => {
const start = toDate(schedule.startDate);
const end = toDate(schedule.endDate);
const { startOffset, duration } = calculateOffsets(
start,
end,
weekStart,
);

return (
<div
key={schedule.id}
className="absolute flex h-8 items-center overflow-hidden rounded p-1 text-xs text-white"
style={{
backgroundColor: getBackgroundColor(start, end),
top: `${index * 2.2 + 1.5}rem`,
left: `calc(${(startOffset / 7) * 100}% + 4px)`,
width: `calc(${(duration / 7) * 100}% - 8px)`,
zIndex: 10,
paddingLeft: "10px",
}}
>
{schedule.title}
</div>
);
});
const events = schedules.map((schedule) => ({
id: schedule.id,
title: schedule.title,
start: schedule.startDate,
end: schedule.endDate,
backgroundColor: getBackgroundColor(
new Date(schedule.startDate),
new Date(schedule.endDate),
),
borderColor: "transparent",
classNames: ["calendar-event"],
}));

const handlePrevMonth = () => {
const calendarApi = calendarRef.current?.getApi();
calendarApi?.prev();
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() - 1);
setCurrentMonth(newDate);
};

const days = getDaysInMonth(currentMonth);
const weeks = Array.from({ length: 5 }, (_, i) =>
days.slice(i * 7, (i + 1) * 7),
);
const handleNextMonth = () => {
const calendarApi = calendarRef.current?.getApi();
calendarApi?.next();
const newDate = new Date(currentMonth);
newDate.setMonth(newDate.getMonth() + 1);
setCurrentMonth(newDate);
};

return (
<div className="mt-4 overflow-hidden rounded-lg border border-gray-200">
const CustomToolbar = () => {
return (
<div className="flex items-center justify-center p-4">
<Image
src="/assets/icons/icon_prev_gray.png"
alt="Previous Month"
width={20}
height={20}
/>
<button onClick={handlePrevMonth} className="focus:outline-none">
<Image
src="/assets/icons/icon_prev_gray.png"
alt="Previous Month"
width={20}
height={20}
/>
</button>
<h2 className="mx-8 text-lg font-semibold text-[#555555]">
{`${currentMonth.getFullYear()}.${String(currentMonth.getMonth() + 1).padStart(2, "0")}`}
{`${currentMonth.getFullYear()}.${String(
currentMonth.getMonth() + 1,
).padStart(2, "0")}`}
</h2>
<Image
src="/assets/icons/icon_next_gray.png"
alt="Next Month"
width={20}
height={20}
/>
<button onClick={handleNextMonth} className="focus:outline-none">
<Image
src="/assets/icons/icon_next_gray.png"
alt="Next Month"
width={20}
height={20}
/>
</button>
</div>
<div className="grid grid-cols-7 gap-4 px-4 py-2 text-center text-xs font-medium text-gray-500">
{["일", "월", "화", "수", "목", "금", "토"].map((day) => (
<div key={day}>{day}</div>
))}
);
};

const renderEventContent = (eventInfo: EventContentArg) => {
return (
<div className="overflow-hidden whitespace-nowrap px-2.5 py-1 text-xs text-white">
{eventInfo.event.title}
</div>
<div className="border-gray-00 border-t">
<div className="grid grid-cols-7 gap-px bg-gray-100">
{weeks.map((week, weekIndex) => (
<React.Fragment key={weekIndex}>
{week.map((day) => (
<div
key={day.toString()}
className={`relative flex h-32 items-start justify-center bg-white p-2 ${
day.getMonth() !== currentMonth.getMonth()
? "text-gray-400"
: ""
}`}
>
<span className="text-xs">{day.getDate()}</span>
</div>
))}
<div
className="relative col-span-7"
style={{ height: "0", marginTop: "-7.5rem" }}
>
{renderSchedules(week)}
</div>
</React.Fragment>
))}
</div>
);
};

return (
<div className="mt-4 overflow-hidden rounded-lg border border-gray-200">
<CustomToolbar />
<div className="calendar-container [&_.calendar-event]:flex [&_.calendar-event]:h-8 [&_.calendar-event]:items-center [&_.fc-col-header-cell]:border-0 [&_.fc-col-header-cell]:border-b [&_.fc-col-header-cell]:border-gray-200 [&_.fc-col-header-cell]:py-2 [&_.fc-col-header-cell]:text-center [&_.fc-col-header-cell]:text-[11px] [&_.fc-col-header-cell]:font-medium [&_.fc-col-header-cell]:text-gray-500 [&_.fc-day-other_.fc-daygrid-day-number]:text-gray-400 [&_.fc-daygrid-day-number]:flex [&_.fc-daygrid-day-number]:h-8 [&_.fc-daygrid-day-number]:w-full [&_.fc-daygrid-day-number]:items-center [&_.fc-daygrid-day-number]:justify-center [&_.fc-daygrid-day-number]:text-[11px] [&_.fc-daygrid-event]:mx-1 [&_.fc-daygrid-event]:rounded-md [&_.fc-event-title]:overflow-hidden [&_.fc-scrollgrid-section>td]:!border-b-0 [&_.fc-scrollgrid-section>td]:!border-r-0 [&_.fc-scrollgrid-section>th]:!border-r-0 [&_.fc-scrollgrid]:border-0 [&_td]:!border-b-0 [&_td]:!border-r-0">
{" "}
<FullCalendar
ref={calendarRef}
plugins={[dayGridPlugin, interactionPlugin]}
initialView="dayGridMonth"
initialDate="2023-01-01"
headerToolbar={false}
events={events}
eventContent={renderEventContent}
height="auto"
firstDay={0}
eventDisplay="block"
dayMaxEvents={false}
eventTimeFormat={{
hour: "numeric",
minute: "2-digit",
meridiem: false,
}}
dayCellClassNames="h-32"
dayHeaderContent={({ date }) => {
const days = ["일", "월", "화", "수", "목", "금", "토"];
return days[date.getDay()];
}}
/>
</div>
</div>
);
Expand Down
Loading