+
{subTitle}
)}
diff --git a/src/create-schedule/components/ShowDuration.tsx b/src/create-schedule/components/ShowDuration.tsx
new file mode 100644
index 00000000..5b8d4162
--- /dev/null
+++ b/src/create-schedule/components/ShowDuration.tsx
@@ -0,0 +1,21 @@
+import { handleDuration } from "@create-schedule/util";
+import { useEffect, useState } from "react";
+import { Start2EndTime } from "@shared/types";
+
+interface ShowDurationProps {
+ time: Start2EndTime;
+}
+
+const ShowDuration = ({ time }: ShowDurationProps) => {
+ const [duration, setDuration] = useState(0);
+
+ useEffect(() => {
+ handleDuration({ time, setDuration });
+ }, [time]);
+
+ return (
+
{`(${duration}시간)`}
+ );
+};
+
+export default ShowDuration;
diff --git a/src/create-schedule/components/ShowSelectedDate.tsx b/src/create-schedule/components/ShowSelectedDate.tsx
new file mode 100644
index 00000000..2830b317
--- /dev/null
+++ b/src/create-schedule/components/ShowSelectedDate.tsx
@@ -0,0 +1,13 @@
+const ShowSelectedDate = () => {
+ // TODO: Date 객체 사용
+ return (
+
+ );
+};
+
+export default ShowSelectedDate;
diff --git a/src/create-schedule/components/ShowSelectedItem.tsx b/src/create-schedule/components/ShowSelectedItem.tsx
new file mode 100644
index 00000000..0494b8f0
--- /dev/null
+++ b/src/create-schedule/components/ShowSelectedItem.tsx
@@ -0,0 +1,16 @@
+import { useRecoilValue } from "recoil";
+import { selectedScheduleItem } from "@shared/recoil";
+import CategoryFrame from "./CategoryFrame";
+
+const ShowSelectedItem = () => {
+ const selectedItem = useRecoilValue(selectedScheduleItem);
+
+ return (
+
+
선택 아이템
+
{selectedItem && }
+
+ );
+};
+
+export default ShowSelectedItem;
diff --git a/src/create-schedule/components/Template.tsx b/src/create-schedule/components/Template.tsx
index 54bed351..e6687ee5 100644
--- a/src/create-schedule/components/Template.tsx
+++ b/src/create-schedule/components/Template.tsx
@@ -1,8 +1,23 @@
import { templateContent } from "@create-schedule/constants";
import { ScheduleCard } from "@shared/components";
-const Template = () => {
- return
;
+interface TemplateProps {
+ isClicked: {
+ template: boolean;
+ custom: boolean;
+ };
+ onClick: (key: "template" | "custom") => void;
+}
+
+const Template = ({ isClicked, onClick }: TemplateProps) => {
+ return (
+
+ );
};
export default Template;
diff --git a/src/create-schedule/components/TemplateButton.tsx b/src/create-schedule/components/TemplateButton.tsx
index 9b593c7c..a261fc06 100644
--- a/src/create-schedule/components/TemplateButton.tsx
+++ b/src/create-schedule/components/TemplateButton.tsx
@@ -4,15 +4,19 @@ import MakeScheduleButton from "./MakeScheduleButton";
interface TemplateButtonProps {
clickedContent: number | null;
+ template: boolean;
+ custom: boolean;
+ onClick: (key: "template" | "custom") => void;
}
-const TemplateButton = ({ clickedContent }: TemplateButtonProps) => {
+const TemplateButton = ({
+ template,
+ custom,
+ clickedContent,
+ onClick,
+}: TemplateButtonProps) => {
const setCurrentProgress = useSetRecoilState(currentProgress);
- const handleCurrentProgress = () => {
- setCurrentProgress((prev) => prev + 1);
- };
-
const handleSelectTemplate = () => {
if (clickedContent) {
setCurrentProgress((prev) => prev + 1);
@@ -22,14 +26,23 @@ const TemplateButton = ({ clickedContent }: TemplateButtonProps) => {
return (
onClick("custom")}
/>
);
diff --git a/src/create-schedule/components/TemplateRecommend.tsx b/src/create-schedule/components/TemplateRecommend.tsx
index f11c1d45..fc373d71 100644
--- a/src/create-schedule/components/TemplateRecommend.tsx
+++ b/src/create-schedule/components/TemplateRecommend.tsx
@@ -2,14 +2,22 @@ import { SCHEDULE_TITLE } from "@create-schedule/constants";
import ScheduleTitle from "./ScheduleTitle";
import Template from "./Template";
-const TemplateRecommend = () => {
+interface TemplateRecommendProps {
+ isClicked: {
+ template: boolean;
+ custom: boolean;
+ };
+ onClick: (key: "template" | "custom") => void;
+}
+
+const TemplateRecommend = ({ isClicked, onClick }: TemplateRecommendProps) => {
return (
<>
-
+
>
);
};
diff --git a/src/create-schedule/components/TimeDetailSelector.tsx b/src/create-schedule/components/TimeDetailSelector.tsx
new file mode 100644
index 00000000..17374efa
--- /dev/null
+++ b/src/create-schedule/components/TimeDetailSelector.tsx
@@ -0,0 +1,49 @@
+import { useState } from "react";
+import { useRecoilValue } from "recoil";
+import { selectedScheduleItem } from "@shared/recoil";
+import ScheduleRegisterButton from "./ScheduleRegisterButton";
+import ScheduleTimeSelect from "./ScheduleTimeSelect";
+
+const TimeDetailSelector = () => {
+ const selectedItem = useRecoilValue(selectedScheduleItem);
+ const [time, setTime] = useState({
+ start: { hour: `${selectedItem?.selectedTime}시`, minute: "0분" },
+ end: { hour: `${selectedItem?.selectedTime}시`, minute: "15분" },
+ });
+
+ const handleTime = (
+ key: "start" | "end",
+ time: "hour" | "minute",
+ value: number,
+ ) => {
+ setTime((prev) => ({ ...prev, [key]: { ...prev[key], [time]: value } }));
+ };
+
+ return (
+ <>
+
+
+ 15분 단위로 선택이 가능합니다.
+
+
+
+
+
+ >
+ );
+};
+
+export default TimeDetailSelector;
diff --git a/src/create-schedule/components/TimeModal.tsx b/src/create-schedule/components/TimeModal.tsx
new file mode 100644
index 00000000..debe8469
--- /dev/null
+++ b/src/create-schedule/components/TimeModal.tsx
@@ -0,0 +1,17 @@
+import ScheduleTitle from "./ScheduleTitle";
+import ShowSelectedDate from "./ShowSelectedDate";
+import ShowSelectedItem from "./ShowSelectedItem";
+import TimeDetailSelector from "./TimeDetailSelector";
+
+const TimeModal = () => {
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default TimeModal;
diff --git a/src/create-schedule/components/TimeTable.tsx b/src/create-schedule/components/TimeTable.tsx
new file mode 100644
index 00000000..8947a421
--- /dev/null
+++ b/src/create-schedule/components/TimeTable.tsx
@@ -0,0 +1,116 @@
+import { PIXEL } from "@create-schedule/constants";
+import {
+ calculateItemHeight,
+ calculateTableHeight,
+ calculateTop,
+} from "@create-schedule/util";
+import { DragEvent, useEffect, useState } from "react";
+import { useRecoilState, useRecoilValue } from "recoil";
+import { useModal } from "@shared/hook";
+import { appliedScheduleItem, selectedScheduleItem } from "@shared/recoil";
+import { AppliedItem, CategoryItem } from "@shared/types";
+import CategoryFrame from "./CategoryFrame";
+
+export interface TableArrayType {
+ time: number;
+ cellHeight: number;
+}
+
+interface TimeTableProps {
+ callType: "template" | "custom";
+ initialData?: AppliedItem[];
+}
+
+const TimeTable = ({ callType, initialData }: TimeTableProps) => {
+ const [tableArray, setTableArray] = useState
(
+ Array.from({ length: 25 }, (_, index) => ({
+ time: index,
+ cellHeight: PIXEL.cellHeight,
+ })),
+ );
+
+ const { openModal } = useModal();
+ const appliedItem = useRecoilValue(appliedScheduleItem);
+ const [selectedItem, setSelectedItem] = useRecoilState(selectedScheduleItem);
+
+ const onDrop = (e: DragEvent, index: number) => {
+ setSelectedItem((prev) => ({
+ ...(prev as CategoryItem),
+ selectedTime: index,
+ }));
+ if (
+ selectedItem?.title &&
+ selectedItem.category &&
+ selectedItem.tagBackground
+ ) {
+ openModal({ contentId: "scheduleTimeSelector", isHeaderCloseBtn: true });
+ }
+ e.preventDefault();
+ };
+
+ const renderItems = (
+ items: AppliedItem[],
+ cellHeight: number,
+ index: number,
+ ) => {
+ return items.map((item, _index) => {
+ const { type, itemHeight } = calculateItemHeight(
+ item.startTime,
+ item.endTime,
+ );
+ const top = calculateTop(cellHeight);
+
+ if (item.startTime.hour === index)
+ return (
+
+
+
+ );
+ });
+ };
+
+ useEffect(() => {
+ calculateTableHeight({ appliedItem, tableArray, setTableArray });
+ }, [appliedItem]);
+
+ return (
+
+
+ {tableArray.map(({ time, cellHeight }, index) => (
+ onDrop(e, index)}
+ onDragOver={(e) => e.preventDefault()}
+ >
+
+ {time}:00
+
+ {callType === "template"
+ ? initialData && renderItems(initialData, cellHeight, index)
+ : appliedItem && renderItems(appliedItem, cellHeight, index)}
+
+ |
+
+ ))}
+
+
+ );
+};
+
+export default TimeTable;
diff --git a/src/create-schedule/components/TimeTableContainer.tsx b/src/create-schedule/components/TimeTableContainer.tsx
new file mode 100644
index 00000000..be6a7d5e
--- /dev/null
+++ b/src/create-schedule/components/TimeTableContainer.tsx
@@ -0,0 +1,19 @@
+import DateContainer from "./DateContainer";
+import TimeTable from "./TimeTable";
+
+interface TimeTableContainerProps {}
+
+const TimeTableContainer = ({}: TimeTableContainerProps) => {
+ return (
+ <>
+
+ >
+ );
+};
+
+export default TimeTableContainer;
diff --git a/src/create-schedule/components/index.ts b/src/create-schedule/components/index.ts
index 34b8e9f8..2dad7c63 100644
--- a/src/create-schedule/components/index.ts
+++ b/src/create-schedule/components/index.ts
@@ -1,12 +1,21 @@
export { default as BasicInfo } from "./BasicInfo";
export { default as BlockShowing } from "./BlockShowing";
+export { default as Categories } from "./Categories";
+export { default as CategoryFrame } from "./CategoryFrame";
+export { default as CategoryItems } from "./CategoryItems";
+export { default as CategoryTagBox } from "./CategoryTagBox";
export { default as Continue } from "./Continue";
+export { default as CurrentDate } from "./CurrentDate";
export { default as CurrentPage } from "./CurrentPage";
export { default as CurrentRecommendTag } from "./CurrentRecommendTag";
+export { default as CustomItems } from "./CustomItems";
export { default as DateCityHandler } from "./DateCityHandler";
export { default as DateCityInput } from "./DateCityInput";
+export { default as DateMoveButton } from "./DateMoveButton";
+export { default as DragnDropContainer } from "./DragnDropContainer";
export { default as FillPlan } from "./FillPlan";
export { default as FinishWriting } from "./FinishWriting";
+export { default as MakeCustomItem } from "./MakeCustomItem";
export { default as MakeScheduleButton } from "./MakeScheduleButton";
export { default as MenuContent } from "./MenuContent";
export { default as MenuContentContainer } from "./MenuContentContainer";
@@ -16,22 +25,31 @@ export { default as PlanDefaultInfo } from "./PlanDefaultInfo";
export { default as PlanMainImage } from "./PlanMainImage";
export { default as PlanSideBar } from "./PlanSideBar";
export { default as PlanTitleInput } from "./PlanTitleInput";
-export { default as RemainChar } from "./RemainChar";
export { default as RemainContent } from "./RemainContent";
export { default as Remains } from "./Remains";
export { default as ScheduleNextButton } from "./ScheduleNextButton";
+export { default as ScheduleRegisterButton } from "./ScheduleRegisterButton";
export { default as ScheduleTagInput } from "./ScheduleTagInput";
export { default as ScheduleTagTemplate } from "./ScheduleTagTemplate";
+export { default as ScheduleTimeSelect } from "./ScheduleTimeSelect";
export { default as ScheduleTitle } from "./ScheduleTitle";
export { default as SelectCity } from "./SelectCity";
export { default as SelectDate } from "./SelectDate";
export { default as SelectMainImage } from "./SelectMainImage";
export { default as SelectMainImageBox } from "./SelectMainImageBox";
+export { default as ShowDuration } from "./ShowDuration";
+export { default as ShowSelectedDate } from "./ShowSelectedDate";
+export { default as ShowSelectedItem } from "./ShowSelectedItem";
export { default as SideBarIntro } from "./SideBarIntro";
export { default as SideBarMenuBox } from "./SideBarMenuBox";
+export { default as TagInput } from "./TagInput";
export { default as TagNTemplate } from "./TagNTemplate";
export { default as TagRecommend } from "./TagRecommend";
export { default as Template } from "./Template";
export { default as TemplateButton } from "./TemplateButton";
export { default as TemplateRecommend } from "./TemplateRecommend";
+export { default as TimeDetailSelector } from "./TimeDetailSelector";
+export { default as TimeModal } from "./TimeModal";
+export { default as TimeTable } from "./TimeTable";
+export { default as TimeTableContainer } from "./TimeTableContainer";
export { default as Title } from "./Title";
diff --git a/src/create-schedule/constants/index.ts b/src/create-schedule/constants/index.ts
index b9d133e1..de041a3c 100644
--- a/src/create-schedule/constants/index.ts
+++ b/src/create-schedule/constants/index.ts
@@ -1,4 +1,9 @@
-import { PlanSubTitle, PlanTitle } from "@shared/types";
+import {
+ CategoryItem,
+ CategoryTags,
+ PlanSubTitle,
+ PlanTitle,
+} from "@shared/types";
export const TITLE: PlanTitle = {
remains: "잠깐, 작성중이던 일정이 있어요",
@@ -24,6 +29,7 @@ export const SCHEDULE_TITLE = {
tagRecommend: "이런 태그는 어떤가요?",
templateRecommend: (nickname: string) =>
`${nickname} 님을 위한 추천 일정 템플릿이 있어요!`,
+ categoryItems: "카테고리별 아이템",
};
export const SCHEDULE_SUBTITLE = {
@@ -33,6 +39,93 @@ export const SCHEDULE_SUBTITLE = {
city: "일정을 수행할 위치를 선택해주세요.",
};
+export const PIXEL = {
+ itemHeight: 21,
+ cellHeight: 35,
+ padding: 6,
+ gap: 1,
+};
+
+export const TIME = {
+ hour: 60,
+};
+
+export const CATEGORY_TAGS: { imageSrc: string; title: CategoryTags }[] = [
+ { imageSrc: "/images/samples/category_all.svg", title: "전체" },
+ { imageSrc: "/images/samples/category_movie.svg", title: "영화" },
+ { imageSrc: "/images/samples/category_festival.svg", title: "축제" },
+ { imageSrc: "/images/samples/category_camping.svg", title: "캠핑" },
+ { imageSrc: "/images/samples/category_tour.svg", title: "관광" },
+ { imageSrc: "/images/samples/category_shopping.svg", title: "쇼핑" },
+ { imageSrc: "/images/samples/category_food.svg", title: "음식점" },
+ { imageSrc: "/images/samples/category_culture.svg", title: "문화생활" },
+ { imageSrc: "/images/samples/category_hiking.svg", title: "등산" },
+ { imageSrc: "/images/samples/category_etc.svg", title: "기타" },
+];
+
+export const category: CategoryItem[] = [
+ {
+ category: "쇼핑",
+ title: "용산 아이파크몰",
+ city: "용산",
+ tagBackground: "bg-[#A3FAF2]",
+ },
+ {
+ category: "쇼핑",
+ title: "용산 아이파크몰",
+ city: "용산",
+ tagBackground: "bg-[#FFE779]",
+ },
+ {
+ category: "쇼핑",
+ title: "용산 아이파크몰",
+ city: "용산",
+ tagBackground: "bg-[#FFC395]",
+ },
+ {
+ category: "쇼핑",
+ title: "용산 아이파크몰",
+ city: "용산",
+ tagBackground: "bg-[#CFE1FF]",
+ },
+ {
+ category: "문화생활",
+ title: "마블 영화 감상",
+ city: "연남동",
+ tagBackground: "bg-[#A3FAF2]",
+ },
+ {
+ category: "문화생활",
+ title: "도서관 가서 신간 읽기",
+ city: "연남동",
+ tagBackground: "bg-[#FFE779]",
+ },
+ {
+ category: "문화생활",
+ title: "국립미술관 가서 전시 관람",
+ city: "연남동",
+ tagBackground: "bg-[#FFC395]",
+ },
+ {
+ category: "문화생활",
+ title: "독서모임 참가하기",
+ city: "연남동",
+ tagBackground: "bg-[#CFE1FF]",
+ },
+ {
+ category: "문화생활",
+ title: "예술의 전당에서 뮤지컬 관람",
+ city: "연남동",
+ tagBackground: "bg-[#DDD1FF]",
+ },
+ {
+ category: "문화생활",
+ title: "오일파스텔 그림그리기",
+ city: "연남동",
+ tagBackground: "bg-[#FFDCDC]",
+ },
+];
+
export const templateContent = [
{
id: 1,
@@ -63,3 +156,25 @@ export const templateContent = [
requiredTime: "2~3일 일정",
},
];
+
+export const itemColor = [
+ "bg-[#A3FAF2]",
+ "bg-[#FFE779]",
+ "bg-[#FFC395]",
+ "bg-[#CFE1FF]",
+ "bg-[#DDD1FF]",
+ "bg-[#FFDCDC]",
+ "bg-[#9BF2CE]",
+ "bg-[#FFB8B4]",
+ "bg-[#95CCFF]",
+ "bg-[#EEB785]",
+ "bg-[#FFD1EA]",
+ "bg-[#D8D8D8]",
+ "bg-[#D8B9F8]",
+ "bg-[#FF9292]",
+ "bg-[#A1A1A1]",
+];
+
+export const TITLE_MAX_LENGTH = 40;
+
+export const WEEK = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
diff --git a/src/create-schedule/util/index.ts b/src/create-schedule/util/index.ts
new file mode 100644
index 00000000..3fc44e70
--- /dev/null
+++ b/src/create-schedule/util/index.ts
@@ -0,0 +1,228 @@
+import { TableArrayType } from "@create-schedule/components/TimeTable";
+import { PIXEL, TIME, WEEK } from "@create-schedule/constants";
+import { Dispatch, SetStateAction } from "react";
+import { AppliedItem, CategoryTags, Start2EndTime } from "@shared/types";
+
+export const calculateItemHeight = (
+ start: { hour: number; minute: number },
+ end: { hour: number; minute: number },
+) => {
+ const { hour: startHour, minute: startMin } = start;
+ const { hour: endHour, minute: endMin } = end;
+ const hourGap = endHour - startHour;
+
+ if (endMin < startMin) {
+ if (hourGap === 1 && endMin === 0) {
+ return { type: "sameCell", itemHeight: PIXEL.itemHeight };
+ } else if (hourGap === 1 && endMin !== 0) {
+ return {
+ type: "otherCell",
+ itemHeight:
+ PIXEL.itemHeight +
+ ((TIME.hour + endMin - startMin) / TIME.hour) * PIXEL.cellHeight -
+ PIXEL.padding * 2,
+ };
+ } else if (hourGap > 1 && endMin === 0) {
+ return {
+ type: "otherCell",
+ itemHeight:
+ (hourGap - 1) * PIXEL.cellHeight + PIXEL.itemHeight + PIXEL.gap * 2,
+ };
+ }
+ return {
+ type: "ohterCell",
+ itemHeight:
+ PIXEL.cellHeight * (hourGap - 1) +
+ PIXEL.itemHeight +
+ PIXEL.padding * 2 +
+ PIXEL.gap * (hourGap + 1),
+ };
+ }
+ if (endHour === startHour || (hourGap === 1 && endMin === 0)) {
+ return { type: "sameCell", itemHeight: PIXEL.itemHeight };
+ } else if (hourGap >= 1 && endMin !== 0) {
+ return {
+ type: "otherCell",
+ itemHeight:
+ PIXEL.cellHeight * hourGap +
+ PIXEL.itemHeight * 0.25 -
+ PIXEL.padding +
+ PIXEL.gap * hourGap,
+ };
+ }
+
+ return {
+ type: "otherCell",
+ itemHeight: PIXEL.cellHeight * hourGap - PIXEL.padding * 2,
+ };
+};
+
+export const calculateTop = (cellHeight: number) => {
+ if (cellHeight === 35) {
+ return PIXEL.padding;
+ } else if (cellHeight > 35 && cellHeight <= 57) {
+ return PIXEL.itemHeight + PIXEL.gap + PIXEL.padding;
+ } else if (cellHeight > 57 && cellHeight <= 79) {
+ return (PIXEL.itemHeight + PIXEL.gap) * 2 + PIXEL.padding;
+ }
+ return (PIXEL.itemHeight + PIXEL.gap) * 3 + PIXEL.padding;
+};
+
+interface CalculateTableHeightProps {
+ appliedItem: AppliedItem[] | null;
+ tableArray: TableArrayType[];
+ setTableArray: Dispatch>;
+}
+
+export const calculateTableHeight = ({
+ appliedItem,
+ tableArray,
+ setTableArray,
+}: CalculateTableHeightProps) => {
+ if (appliedItem) {
+ const newTableArray = tableArray.map(({ time, cellHeight }, index) => {
+ const startTableItems = appliedItem.filter(
+ (item) => item.startTime.hour === time,
+ ).length;
+
+ return {
+ time,
+ cellHeight:
+ startTableItems > 0
+ ? PIXEL.cellHeight +
+ (startTableItems - 1) * PIXEL.itemHeight +
+ PIXEL.gap * (startTableItems - 1)
+ : cellHeight,
+ };
+ });
+ setTableArray(newTableArray);
+ }
+};
+
+export const handleTimeFormat = (time: Start2EndTime) => {
+ const startHour = parseInt(time.start.hour, 10);
+ const startMin = parseInt(time.start.minute, 10);
+ const endHour = parseInt(time.end.hour, 10);
+ const endMin = parseInt(time.end.minute, 10);
+
+ return { startHour, startMin, endHour, endMin };
+};
+
+interface HandleDurationProps {
+ time: Start2EndTime;
+ setDuration: Dispatch>;
+}
+
+export const handleDuration = ({ time, setDuration }: HandleDurationProps) => {
+ const { startHour, startMin, endHour, endMin } = handleTimeFormat(time);
+ if (endMin < startMin) {
+ const hour = endHour - startHour - 1;
+ const min = 60 + endMin - startMin;
+ setDuration(hour + min / 60);
+
+ return;
+ }
+
+ const hour = endHour - startHour;
+ const min = endMin - startMin;
+ setDuration(hour + min / 60);
+};
+
+export const checkDuplication = (
+ startHour: number,
+ startMin: number,
+ endHour: number,
+ endMin: number,
+ appliedItem: AppliedItem[] | null,
+) => {
+ // 이미 존재하는 테이블 아이템에 대해 검사
+ if (appliedItem) {
+ const isDuplicated = appliedItem.map((item) => {
+ const startRange = item.startTime.hour * 60 + item.startTime.minute;
+ const endRange = item.endTime.hour * 60 + item.endTime.minute;
+ const newItemStartRange = startHour * 60 + startMin;
+ const newItemEndRange = endHour * 60 + endMin;
+
+ if (startRange <= newItemStartRange && endRange > newItemStartRange) {
+ return true;
+ } else if (newItemEndRange > startRange && newItemEndRange <= endRange) {
+ return true;
+ } else if (startRange > newItemStartRange && endRange < newItemEndRange) {
+ return true;
+ }
+ return false;
+ });
+
+ return isDuplicated;
+ }
+ // 테이블이 비었을 경우
+ return [false];
+};
+
+export const sortByStartTime = (appliedItem: AppliedItem[]) => {
+ appliedItem.sort((a, b) => {
+ if (a.startTime.hour !== b.startTime.hour) {
+ return a.startTime.hour - b.startTime.hour;
+ }
+ return a.startTime.minute - b.startTime.minute;
+ });
+};
+
+export const handleImageSrc = (category: CategoryTags) => {
+ switch (category) {
+ case "영화":
+ return "/images/samples/category_movie.svg";
+ case "축제":
+ return "/images/samples/category_festival.svg";
+ case "캠핑":
+ return "/images/samples/category_camping.svg";
+ case "관광":
+ return "/images/samples/category_tour.svg";
+ case "쇼핑":
+ return "/images/samples/category_shopping.svg";
+ case "음식점":
+ return "/images/samples/category_food.svg";
+ case "문화생활":
+ return "/images/samples/category_culture.svg";
+ case "등산":
+ return "/images/samples/category_hiking.svg";
+ case "기타":
+ return "/images/samples/category_etc.svg";
+ }
+};
+
+export const handleDateFormat = (value: Date) => {
+ const year = value.getFullYear();
+ const month = String(value.getMonth() + 1).padStart(2, "0");
+ const day = String(value.getDate()).padStart(2, "0");
+
+ return `${year}.${month}.${day}`;
+};
+
+export const handleCurrentDate = (prev: Date | null, type: "next" | "prev") => {
+ if (prev && type === "next") {
+ const newDate = new Date(
+ prev.getFullYear(),
+ prev.getMonth(),
+ prev.getDate() + 1,
+ );
+
+ return newDate;
+ } else if (prev && type === "prev") {
+ const newDate = new Date(
+ prev.getFullYear(),
+ prev.getMonth(),
+ prev.getDate() - 1,
+ );
+
+ return newDate;
+ }
+
+ return null;
+};
+
+export const getDay = (value: Date) => {
+ const currentDayIndex = value.getDay();
+
+ return WEEK[currentDayIndex];
+};
diff --git a/src/modalContent/CalendarSelector.tsx b/src/modalContent/CalendarSelector.tsx
index c9153417..8fd5807a 100644
--- a/src/modalContent/CalendarSelector.tsx
+++ b/src/modalContent/CalendarSelector.tsx
@@ -13,10 +13,7 @@ const CalendarSelector = ({ type }: CalendarSelectorProps) => {
const setAnswer = useSetRecoilState(scheduleAnswers);
const handleDate = (date: Date) => {
- const year = date.getFullYear();
- const month = date.getMonth() + 1;
- const day = String(date.getDate()).padStart(2, "0");
- setAnswer((prev) => ({ ...prev, [type]: `${year}.${month}.${day}` }));
+ setAnswer((prev) => ({ ...prev, [type]: date }));
closeModal();
};
diff --git a/src/modalContent/CustomScheduleSelector.tsx b/src/modalContent/CustomScheduleSelector.tsx
new file mode 100644
index 00000000..fe8e4f49
--- /dev/null
+++ b/src/modalContent/CustomScheduleSelector.tsx
@@ -0,0 +1,155 @@
+import { ScheduleTitle } from "@create-schedule/components";
+import { itemColor } from "@create-schedule/constants";
+import Image from "next/image";
+import { useState } from "react";
+import { useSetRecoilState } from "recoil";
+import { useModal } from "@shared/hook";
+import { customItem } from "@shared/recoil";
+import { CategoryItem } from "@shared/types";
+
+const CustomScheduleSelector = () => {
+ const { closeModal } = useModal();
+ const [isError, setIsError] = useState(false);
+ const [newItem, setNewItem] = useState({
+ category: "기타",
+ title: "",
+ city: "",
+ tagBackground: "",
+ });
+ const setCustomItems = useSetRecoilState(customItem);
+
+ const handleInput = (key: keyof CategoryItem, value: string) => {
+ setNewItem((prev) => ({ ...prev, [key]: value }));
+ };
+
+ const makeNewItem = () => {
+ if (newItem.category && newItem.tagBackground && newItem.title) {
+ setCustomItems((prev) => {
+ if (prev) {
+ const newArray = [...prev];
+ newArray.push(newItem);
+
+ return newArray;
+ }
+
+ return [newItem];
+ });
+ closeModal();
+ return;
+ }
+ setIsError(true);
+ };
+
+ return (
+
+
+
+ 카테고리
+
+ *
+
+
+
+
+
+
+ 아이템 색
+
+ *
+
+
+
+ {itemColor.map((color, index) => (
+
handleInput("tagBackground", color)}
+ >
+ color
+ {newItem.tagBackground === color && (
+
+
+
+ )}
+
+ ))}
+
+
+
+ 아이템명
+
+ *
+
+
+
handleInput("title", value)}
+ />
+
+
+
위치(선택)
+
handleInput("city", value)}
+ />
+
+
세부내용(선택)
+
+
+
+
+ {isError ? (
+
+ 필수 입력 정보를 모두 입력해주세요.(카테고리, 아이템 색, 아이템명)
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default CustomScheduleSelector;
diff --git a/src/modalContent/ScheduleTimeSelector.tsx b/src/modalContent/ScheduleTimeSelector.tsx
new file mode 100644
index 00000000..de6ca9fc
--- /dev/null
+++ b/src/modalContent/ScheduleTimeSelector.tsx
@@ -0,0 +1,7 @@
+import { TimeModal } from "@create-schedule/components";
+
+const ScheduleTimeSelector = () => {
+ return ;
+};
+
+export default ScheduleTimeSelector;
diff --git a/src/modalContent/TemplatePreview.tsx b/src/modalContent/TemplatePreview.tsx
new file mode 100644
index 00000000..38d0026c
--- /dev/null
+++ b/src/modalContent/TemplatePreview.tsx
@@ -0,0 +1,32 @@
+import { CurrentDate, TimeTable } from "@create-schedule/components";
+import { useModal } from "@shared/hook";
+
+const TemplatePreview = () => {
+ const { closeModal } = useModal();
+ const currentDate = new Date(); // TODO: 날짜 받아오기
+
+ return (
+
+
+ 템플릿 미리 보기
+
+
+
+
+
+ {/* TODO: initialData 받아오기 */}
+
+
+
+
+
+
+ );
+};
+
+export default TemplatePreview;
diff --git a/src/modalContent/index.tsx b/src/modalContent/index.tsx
index 769a22e4..d4db894d 100644
--- a/src/modalContent/index.tsx
+++ b/src/modalContent/index.tsx
@@ -1,6 +1,9 @@
import { ModalContentId } from "@shared/recoil/modal";
import CalendarSelector from "./CalendarSelector";
+import CustomScheduleSelector from "./CustomScheduleSelector";
import RecruitManage, { RecruitManageProps } from "./RecruitManage";
+import ScheduleTimeSelector from "./ScheduleTimeSelector";
+import TemplatePreview from "./TemplatePreview";
import ThumbnailSelector, { ThumbnailSelectorProps } from "./ThumbnailSelector";
export interface ModalContentProps {
@@ -18,6 +21,12 @@ const ModalContent = (modalProps: ModalContentProps) => {
return ;
case "calendarSelector_end":
return ;
+ case "scheduleTimeSelector":
+ return ;
+ case "customScheduleSelector":
+ return ;
+ case "templatePreview":
+ return ;
default:
return <>>;
}
diff --git a/src/shared/components/ScheduleCard.tsx b/src/shared/components/ScheduleCard.tsx
index 729c221f..0ccad844 100644
--- a/src/shared/components/ScheduleCard.tsx
+++ b/src/shared/components/ScheduleCard.tsx
@@ -1,5 +1,6 @@
import { Continue, TemplateButton } from "@create-schedule/components";
import { useState } from "react";
+import { useModal } from "@shared/hook";
// TODO: 일정 타입 지정
export interface Schedule {
@@ -14,12 +15,27 @@ export interface Schedule {
interface ScheduleCardProps {
content: Schedule[];
callType: "remain" | "template";
+ isClicked?: {
+ template: boolean;
+ custom: boolean;
+ };
+ onClick?: (key: "template" | "custom") => void;
}
-const ScheduleCard = ({ content, callType }: ScheduleCardProps) => {
+const ScheduleCard = ({
+ content,
+ callType,
+ isClicked,
+ onClick: handleClickCard,
+}: ScheduleCardProps) => {
+ const { openModal } = useModal();
const [clickedContent, setClickedContent] = useState(null);
- const handleClick = (index: number) => {
+ const onClick = (index: number) => {
+ if (callType === "template" && handleClickCard) {
+ handleClickCard("template");
+ openModal({ contentId: "templatePreview", isHeaderCloseBtn: true });
+ }
setClickedContent(index);
};
@@ -32,7 +48,7 @@ const ScheduleCard = ({ content, callType }: ScheduleCardProps) => {
className={`${
clickedContent === index ? "border-[#22AFFF]" : "border-[#E0E0E0]"
} w-[151px] h-[196px] border rounded-[5px] cursor-pointer`}
- onClick={() => handleClick(index)}
+ onClick={() => onClick(index)}
>
{_content.imageSrc}
@@ -60,8 +76,12 @@ const ScheduleCard = ({ content, callType }: ScheduleCardProps) => {
))}
- {callType === "template" ? (
-
+ {callType === "template" && handleClickCard && isClicked ? (
+
) : (
)}
diff --git a/src/shared/recoil/createSchedule.tsx b/src/shared/recoil/createSchedule.tsx
index 318a3b2c..bb2a9583 100644
--- a/src/shared/recoil/createSchedule.tsx
+++ b/src/shared/recoil/createSchedule.tsx
@@ -1,24 +1,54 @@
+import { uniqueId } from "lodash";
import { atom } from "recoil";
-import { CurrentPageType, ScheduleAnswerType } from "@shared/types";
+import {
+ AppliedItem,
+ CategoryItem,
+ CurrentPageType,
+ ScheduleAnswerType,
+ SelectedCategoryItem,
+} from "@shared/types";
export const currentPageName = atom
({
- key: "currentPage",
+ key: `currentSchedulePage/${uniqueId()}`,
default: "작성 중인 일정",
});
export const currentProgress = atom({
- key: "currentProgress",
+ key: `currentProgress/${uniqueId()}`,
default: 1,
});
export const scheduleAnswers = atom({
- key: "scheduleAnswers",
+ key: `scheduleAnswers/${uniqueId()}`,
default: {
title: "",
imageSrc: "",
- startedAt: "",
- endedAt: "",
+ startedAt: null,
+ endedAt: null,
city: "",
tag: [],
+ items: [
+ {
+ tag: "",
+ title: "",
+ background: "",
+ city: "",
+ },
+ ],
},
});
+
+export const selectedScheduleItem = atom({
+ key: `selectedScheduleItem/${uniqueId()}`,
+ default: null,
+});
+
+export const appliedScheduleItem = atom({
+ key: `appliedScheduleItem${uniqueId()}`,
+ default: null,
+});
+
+export const customItem = atom({
+ key: `customScheduleItem${uniqueId()}`,
+ default: null,
+});
diff --git a/src/shared/recoil/modal.tsx b/src/shared/recoil/modal.tsx
index 892a6219..cc64bf18 100644
--- a/src/shared/recoil/modal.tsx
+++ b/src/shared/recoil/modal.tsx
@@ -89,4 +89,7 @@ export type ModalContentId =
| "thumbnailSelector"
| "calendarSelector_start"
| "calendarSelector_end"
+ | "scheduleTimeSelector"
+ | "customScheduleSelector"
+ | "templatePreview"
| "RecruitManage";
diff --git a/src/shared/recoil/planTitle.tsx b/src/shared/recoil/planTitle.tsx
deleted file mode 100644
index 134da2f5..00000000
--- a/src/shared/recoil/planTitle.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { SUBTITLE, TITLE } from "@create-schedule/constants";
-import { atom } from "recoil";
-import { PlanSubTitle, PlanTitle } from "@shared/types";
-
-export const planTitle = atom({
- key: "planTitle",
- default: {
- remains: TITLE.remains,
- nthPlan: (nickname: string, number: number) =>
- `${nickname} 님의 ${number}번째 일정`,
- tag: "태그 및 일정 템플릿",
- fill: "일정 채우기",
- finish: "작성 마무리",
- },
-});
-
-export const planSubTitle = atom({
- key: "planSubTitle",
- default: {
- fighting: (nickname: string) => SUBTITLE.fighting(nickname),
- withyou: SUBTITLE.withyou,
- fillyourplan: (nickname: string) => SUBTITLE.fillyourplan(nickname),
- },
-});
diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts
index 26e747c7..bb5a1e3b 100644
--- a/src/shared/types/index.ts
+++ b/src/shared/types/index.ts
@@ -54,11 +54,64 @@ export interface PlanSubTitle {
export interface ScheduleAnswerType {
title: string;
imageSrc: string;
- startedAt: string;
- endedAt: string;
+ startedAt: Date | null;
+ endedAt: Date | null;
city: string;
tag: string[];
+ items: [
+ {
+ tag: string;
+ title: string;
+ background: string;
+ city: string;
+ },
+ ];
}
export type CalendarSelectorType = "startedAt" | "endedAt";
+
+export interface CategoryItem {
+ category: CategoryTags;
+ title: string;
+ city: string;
+ tagBackground: string;
+}
+
+export interface SelectedCategoryItem extends CategoryItem {
+ selectedTime: number;
+}
+
+export interface AppliedItem extends CategoryItem {
+ startTime: {
+ hour: number;
+ minute: number;
+ };
+ endTime: {
+ hour: number;
+ minute: number;
+ };
+}
+
+export interface Start2EndTime {
+ start: {
+ hour: string;
+ minute: string;
+ };
+ end: {
+ hour: string;
+ minute: string;
+ };
+}
+
+export type CategoryTags =
+ | "전체"
+ | "영화"
+ | "축제"
+ | "캠핑"
+ | "관광"
+ | "쇼핑"
+ | "음식점"
+ | "문화생활"
+ | "등산"
+ | "기타";
export type LoginType = "kakao" | "naver" | "catcher";