Skip to content

Commit

Permalink
Feat: #51 Calendar 기본 UI 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
Seok93 committed Jul 17, 2024
1 parent 59c134e commit 63ca720
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 2 deletions.
86 changes: 86 additions & 0 deletions src/components/task/calendar/CalendarToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useMemo } from 'react';
import { DateTime } from 'luxon';
import {
MdKeyboardArrowLeft,
MdKeyboardArrowRight,
MdKeyboardDoubleArrowLeft,
MdKeyboardDoubleArrowRight,
} from 'react-icons/md';
import { Project } from '@/types/ProjectType';

type CalendarToolbarProp = {
date: Date;
startDate: Project['startDate'];
onClick: (date: Date) => void;
};

export default function CalendarToolbar({ date, startDate, onClick }: CalendarToolbarProp) {
const { year, month } = useMemo(() => DateTime.fromJSDate(date), [date]);

const handlePrevMonthClick = () => {
const prevMonthDate = DateTime.fromJSDate(date).minus({ month: 1 }).toJSDate();
onClick(prevMonthDate);
};
const handlePrevYearClick = () => {
const prevYearDate = DateTime.fromJSDate(date).minus({ year: 1 }).toJSDate();
onClick(prevYearDate);
};
const handleNextMonthClick = () => {
const nextMonthDate = DateTime.fromJSDate(date).plus({ month: 1 }).toJSDate();
onClick(nextMonthDate);
};
const handleNextYearClick = () => {
const nextYearDate = DateTime.fromJSDate(date).plus({ year: 1 }).toJSDate();
onClick(nextYearDate);
};
const handleTodayClick = () => {
const todayDate = DateTime.now().toJSDate();
onClick(todayDate);
};

// ToDo: startDate가 없을 수도 있구나... 그럼 첫태스크가 있는 곳으로 보내야하나
const handleStartDayClick = () => {
if (startDate === null) return;
const projectStartDate = DateTime.fromJSDate(startDate).toJSDate();
onClick(projectStartDate);
};

return (
<section className="flex h-30 flex-wrap items-center justify-center bg-main px-10 *:w-1/3">
<div />
<div className="flex items-center justify-center text-center font-bold text-white">
<button type="button" aria-label="이전 연도" onClick={handlePrevYearClick}>
<MdKeyboardDoubleArrowLeft />
</button>
<button type="button" aria-label="이전 달" onClick={handlePrevMonthClick}>
<MdKeyboardArrowLeft />
</button>
<span className="mx-10">
{year}{month}
</span>
<button type="button" aria-label="다음 달" onClick={handleNextMonthClick}>
<MdKeyboardArrowRight />
</button>
<button type="button" aria-label="다음 연도" onClick={handleNextYearClick}>
<MdKeyboardDoubleArrowRight />
</button>
</div>
<div className="text-right text-emphasis *:ml-5">
<button
type="button"
className="px-8hover:brightness-90 box-border h-20 w-50 rounded-lg bg-button"
onClick={handleStartDayClick}
>
시작일
</button>
<button
type="button"
className="box-border h-20 w-50 rounded-lg bg-button px-8 hover:brightness-90"
onClick={handleTodayClick}
>
당일
</button>
</div>
</section>
);
}
8 changes: 8 additions & 0 deletions src/components/task/calendar/CustomDateHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DateHeaderProps } from 'react-big-calendar';

// ToDo: Header 컴포넌트 수정할 것
export default function CustomDateHeader({ date, drilldownView, isOffRange, label, onDrillDown }: DateHeaderProps) {
console.log(date, drilldownView, isOffRange, label, onDrillDown);
console.log(date.getDate());
return <div>{date.getDate()}</div>;
}
15 changes: 15 additions & 0 deletions src/components/task/calendar/CustomEvent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Event, EventProps } from 'react-big-calendar';
import { TaskWithStatus } from '@/types/TaskType';

export type CustomEvents = Event & {
task: TaskWithStatus;
handleEventClick: (task: TaskWithStatus) => void;
};

export default function CustomEvent({ event }: EventProps<CustomEvents>) {
return (
<div style={{ backgroundColor: event.task.color }} className="overflow-hidden text-ellipsis rounded-md px-3 py-1">
<span>{event.task.name}</span>
</div>
);
}
120 changes: 119 additions & 1 deletion src/pages/project/CalendarPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,121 @@
import { useCallback, useMemo, useState } from 'react';
import { DateTime, Settings } from 'luxon';
import { Calendar, luxonLocalizer, Views } from 'react-big-calendar';
import CalendarToolbar from '@components/task/calendar/CalendarToolbar';
import CustomEvent, { CustomEvents } from '@components/task/calendar/CustomEvent';
import CustomDateHeader from '@components/task/calendar/CustomDateHeader';
import useModal from '@hooks/useModal';
import ModalLayout from '@layouts/ModalLayout';
import ModalPortal from '@components/modal/ModalPortal';
import ModaFormButton from '@components/modal/ModaFormButton';
import { TASK_DUMMY } from '@mocks/mockData';
import { TaskListWithStatus, TaskWithStatus } from '@/types/TaskType';
import 'react-big-calendar/lib/css/react-big-calendar.css';

function getCalendarTask(statusTasks: TaskListWithStatus[]) {
const calendarTasks: TaskWithStatus[] = [];

statusTasks.forEach((statusTask) => {
const { statusId, name: statusName, color, order: statusOrder, tasks } = statusTask;
tasks.forEach((task) => {
calendarTasks.push({ statusId, statusName, color, statusOrder, ...task });
});
});
console.log(calendarTasks);

return calendarTasks;
}

const dt = DateTime.local();
Settings.defaultZone = dt.zoneName;
const localizer = luxonLocalizer(DateTime, { firstDayOfWeek: 7 });

export default function CalendarPage() {
return <div>CalendarPage</div>;
const { showModal, openModal, closeModal } = useModal();
const [selectedTask, setSelectedTask] = useState<TaskWithStatus>();
const [date, setDate] = useState<Date>(() => DateTime.now().toJSDate());

const handleToolbarClick = (date: Date) => setDate(date);

const handleEventClick = (task: TaskWithStatus) => {
setSelectedTask(task);
openModal();
};

const handleSelectEvent = (event: CustomEvents) => {
setSelectedTask(event?.task);
openModal();
};

const handleSelectSlot = useCallback(({ start, end }: { start: Date; end: Date }) => {
console.log(`시작일: ${start}, 마감일: ${end}`);
alert('날짜가 선택되었어요!');
}, []);

const { views, components: customComponents } = useMemo(
() => ({
views: [Views.MONTH, Views.WEEK],
components: {
event: CustomEvent,
month: {
header: () => undefined,
dateHeader: CustomDateHeader,
},
},
}),
[],
);

const state = {
events: getCalendarTask(TASK_DUMMY)
.map((statusTask) => ({
title: statusTask.name,
start: new Date(statusTask.startDate),
end: new Date(statusTask.endDate),
allDays: true,
task: { ...statusTask },
handleEventClick,
}))
.sort((a, b) => a.start.getTime() - b.end.getTime()),
};

// ToDo: 캘린더 스타일 변경을 위해 커스텀 컴포넌트 추가
// ToDo: DnD, Resize 이벤트 추가 생각해보기
// ToDo: 할일 추가 모달 Form 작업 완료시 모달 컴포넌트 분리
// ToDo: react-big-calendar CSS overwrite
// ToDo: onNavigate로 발생하는 warning 해결
// ToDo: 캘린더 크기 전체적으로 조정
// ToDo: 코드 리팩토링
return (
<div className="min-h-375 min-w-375 grow">
<CalendarToolbar date={date} startDate={state.events[0].start} onClick={handleToolbarClick} />
<Calendar
toolbar={false}
localizer={localizer}
defaultView="month"
date={date}
views={views}
events={state.events}
components={customComponents}
titleAccessor="title"
startAccessor="start"
endAccessor="end"
allDayAccessor="allDay"
popup
onSelectEvent={handleSelectEvent}
// selectable
// onSelectSlot={handleSelectSlot}
/>
{showModal && (
<ModalPortal>
<ModalLayout onClose={closeModal}>
<div className="flex h-full flex-col items-center justify-center">
<div>{selectedTask?.name}</div>
<ModaFormButton formId="updateStatusForm" isCreate={false} onClose={closeModal} />
</div>
</ModalLayout>
</ModalPortal>
)}
</div>
);
}
11 changes: 10 additions & 1 deletion src/types/TaskType.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { ProjectStatus } from '@/types/ProjectStatusType';

type RenameKeys<T, R extends { [K in keyof R]: K extends keyof T ? string : never }> = {
[P in keyof T as P extends keyof R ? R[P] : P]: T[P];
};

type StatusKeyMapping = {
name: 'statusName';
order: 'statusOrder';
};

// ToDo: API 설계 완료시 데이터 타입 변경할 것
export type Task = {
taskId: number;
Expand All @@ -11,5 +20,5 @@ export type Task = {
endDate: string;
};

export type TaskWithStatus = ProjectStatus & Task;
export type TaskWithStatus = RenameKeys<ProjectStatus, StatusKeyMapping> & Task;
export type TaskListWithStatus = ProjectStatus & { tasks: Task[] };

0 comments on commit 63ca720

Please sign in to comment.