diff --git a/packages/web/src/components/Event/DailyAttendanceCalendar/index.tsx b/packages/web/src/components/Event/DailyAttendanceCalendar/index.tsx
new file mode 100644
index 00000000..12aed413
--- /dev/null
+++ b/packages/web/src/components/Event/DailyAttendanceCalendar/index.tsx
@@ -0,0 +1,211 @@
+import { memo } from "react";
+
+import { useValueRecoilState } from "@/hooks/useFetchRecoilState";
+
+import MiniCircle from "@/components/MiniCircle";
+
+import moment, { getToday } from "@/tools/moment";
+import theme from "@/tools/theme";
+
+import { ReactComponent as MissionCompleteIcon } from "@/static/events/2023fallMissionComplete.svg";
+
+const getCalendarDates = () => {
+ const startDate = moment("2024-09-06", "YYYY-MM-DD");
+ const endDate = moment("2024-09-24", "YYYY-MM-DD");
+ const endDateOfMonth = moment("2024-09-30", "YYYY-MM-DD");
+ const today = getToday();
+ // const today = moment("2024-09-10", "YYYY-MM-DD"); // FIXME: 배포 전에 수정
+ const date = startDate.clone();
+ date.subtract(date.day(), "day");
+ const event2024FallInfo = useValueRecoilState("event2024FallInfo");
+ const completedDates = event2024FallInfo?.completedQuests.reduce(
+ (acc, { questId, completedAt }) => {
+ if (questId === "dailyAttendance" && completedAt) {
+ acc.push(moment(completedAt).format("YYYY-MM-DD"));
+ }
+ return acc;
+ },
+ [] as string[]
+ );
+
+ const calendar = [];
+
+ for (let i = 0; i < 5; i++) {
+ const week = [];
+ for (let i = 0; i < 7; i++) {
+ let available = null;
+ let checked = false;
+ if (date.isSame(today)) {
+ available = "today";
+ } else if (date.isAfter(startDate) && date.isBefore(today)) {
+ available = "past";
+ } else if (date.isBefore(endDate) && date.isAfter(startDate, "day")) {
+ available = true;
+ }
+
+ if (completedDates?.includes(date.format("YYYY-MM-DD"))) {
+ checked = true;
+ }
+
+ week.push({
+ year: date.year(),
+ month: date.month() + 1,
+ date: date.date(),
+ available,
+ checked,
+ });
+ if (date.isSame(endDateOfMonth)) {
+ break;
+ }
+ date.add(1, "day");
+ }
+ calendar.push(week);
+ }
+ return calendar;
+};
+type DateProps = {
+ index: number;
+ year: number;
+ month: number;
+ date: number;
+ available: string | boolean | null;
+ checked: boolean;
+};
+
+const Date = ({ index, date, available, checked }: DateProps) => {
+ const style = {
+ width: "calc((100% - 36px) / 7)",
+ aspectRatio: "1 / 1",
+ height: "100%",
+ };
+ const styleBox: React.CSSProperties = {
+ ...style,
+ borderRadius: "6px",
+ position: "relative",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ background: available ? theme.white : theme.gray_background,
+ transitionDuration: theme.duration,
+ };
+ const styleDate = {
+ ...theme.font12,
+ letterSpacing: undefined,
+ marginTop: "3px",
+ color:
+ available === "past" || !available
+ ? theme.gray_line
+ : index === 0
+ ? theme.red_text
+ : index === 6
+ ? theme.blue_text
+ : theme.black,
+ };
+ const styleToday: React.CSSProperties = {
+ position: "absolute",
+ top: "calc(50% + 8px)",
+ left: "calc(50% - 2px)",
+ };
+ const styleCompleteIcon: React.CSSProperties = {
+ position: "absolute",
+ height: "34px",
+ width: "34px",
+ };
+
+ if (!date) return
;
+ return (
+
+
{date}
+ {available === "today" && (
+
+
+
+ )}
+ {checked &&
}
+
+ );
+};
+const MemoizedDate = memo(Date);
+
+const DailyAttendanceCalendar = () => {
+ const dateInfo = getCalendarDates();
+
+ const styleMonth: React.CSSProperties = {
+ display: "flex",
+ flexDirection: "column",
+ rowGap: "6px",
+ marginBottom: "5px",
+ };
+ const styleDay: React.CSSProperties = {
+ display: "flex",
+ margin: "12px 0 8px",
+ columnGap: "6px",
+ };
+ const styleDayItem: React.CSSProperties = {
+ width: "calc((100% - 36px) / 7)",
+ fontSize: "10px",
+ height: "12px",
+ textAlign: "center",
+ };
+ const styleWeek = {
+ display: "flex",
+ columnGap: "6px",
+ };
+
+ const week: { color: string; text: string }[] = [
+ { color: theme.red_text, text: "일" },
+ { color: theme.black, text: "월" },
+ { color: theme.black, text: "화" },
+ { color: theme.black, text: "수" },
+ { color: theme.black, text: "목" },
+ { color: theme.black, text: "금" },
+ { color: theme.blue_text, text: "토" },
+ ];
+
+ return (
+
+
+ {week.map((item, index) => (
+
+ {item.text}
+
+ ))}
+
+
+ {dateInfo.map((item, index) => {
+ return (
+
+ {item.map((item, index) => (
+
+ ))}
+
+ );
+ })}
+
+
+ );
+};
+
+export default DailyAttendanceCalendar;
diff --git a/packages/web/src/components/ModalPopup/ModalEvent2024FallDailyAttendance.tsx b/packages/web/src/components/ModalPopup/ModalEvent2024FallDailyAttendance.tsx
new file mode 100644
index 00000000..c745b7c1
--- /dev/null
+++ b/packages/web/src/components/ModalPopup/ModalEvent2024FallDailyAttendance.tsx
@@ -0,0 +1,94 @@
+import { useEffect, useState } from "react";
+
+import { useEvent2024FallQuestComplete } from "@/hooks/event/useEvent2024FallQuestComplete";
+import { useValueRecoilState } from "@/hooks/useFetchRecoilState";
+
+import Button from "@/components/Button";
+import CreditAmountStatusContainer from "@/components/Event/CreditAmountStatusContainer";
+import DailyAttendanceCalendar from "@/components/Event/DailyAttendanceCalendar";
+import Modal from "@/components/Modal";
+import WhiteContainer from "@/components/WhiteContainer";
+
+import moment, { getToday } from "@/tools/moment";
+import theme from "@/tools/theme";
+
+import { ReactComponent as DailyAttendance } from "@/static/events/2024fallDailyAttendance.svg";
+
+type DateSectionProps = {
+ value: Array>;
+ handler: (newValue: Array) => void;
+};
+
+const DateSection = (props: DateSectionProps) => {
+ return (
+
+
+
+ );
+};
+
+interface ModalEvent2024FallDailyAttendanceProps {
+ isOpen: boolean;
+ onChangeIsOpen?: ((isOpen: boolean) => void) | undefined;
+}
+
+const ModalEvent2024FallDailyAttendance = ({
+ isOpen,
+ onChangeIsOpen,
+}: ModalEvent2024FallDailyAttendanceProps) => {
+ const today = getToday();
+
+ const [valueDate, setDate] = useState>>([
+ today.year(),
+ today.month() + 1,
+ today.date(),
+ ]);
+
+ const event2024FallQuestComplete = useEvent2024FallQuestComplete();
+
+ const { isAgreeOnTermsOfEvent = false, completedQuests = [] } =
+ useValueRecoilState("event2024FallInfo") || {};
+
+ const todayInitial = completedQuests?.filter(
+ ({ questId, completedAt }) =>
+ questId === "dailyAttendance" && moment(completedAt).isSame(today, "day")
+ );
+
+ useEffect(() => {
+ const modalOpened = isAgreeOnTermsOfEvent && todayInitial.length === 0;
+
+ if (onChangeIsOpen && modalOpened) {
+ onChangeIsOpen(modalOpened); // 모달 열기 상태 변경
+ event2024FallQuestComplete("dailyAttendance");
+ }
+ }, [isAgreeOnTermsOfEvent, todayInitial.length]);
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default ModalEvent2024FallDailyAttendance;
diff --git a/packages/web/src/components/ModalPopup/index.tsx b/packages/web/src/components/ModalPopup/index.tsx
index 1b98e6eb..64a31bf4 100644
--- a/packages/web/src/components/ModalPopup/index.tsx
+++ b/packages/web/src/components/ModalPopup/index.tsx
@@ -12,6 +12,7 @@ export { default as ModalEvent2023FallRandomBox } from "./ModalEvent2023FallRand
export { default as ModalEvent2024SpringAbuseWarning } from "./ModalEvent2024SpringAbuseWarning";
export { default as ModalEvent2024SpringJoin } from "./ModalEvent2024SpringJoin";
export { default as ModalEvent2024SpringShare } from "./ModalEvent2024SpringShare";
+export { default as ModalEvent2024FallDailyAttendance } from "./ModalEvent2024FallDailyAttendance";
export { default as ModalEvent2024FallItem } from "./ModalEvent2024FallItem";
export { default as ModalEvent2024FallJoin } from "./ModalEvent2024FallJoin";
export { default as ModalEvent2024FallRandomBox } from "./ModalEvent2024FallRandomBox";
diff --git a/packages/web/src/components/Skeleton/index.tsx b/packages/web/src/components/Skeleton/index.tsx
index 6ebff3f7..c08c1ca8 100644
--- a/packages/web/src/components/Skeleton/index.tsx
+++ b/packages/web/src/components/Skeleton/index.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, useMemo } from "react";
+import { ReactNode, useMemo, useState } from "react";
import { useLocation } from "react-router-dom";
import { useEvent2024FallEffect } from "@/hooks/event/useEvent2024FallEffect";
@@ -17,7 +17,10 @@ import {
import HeaderBar from "@/components/Header/HeaderBar";
import Loading from "@/components/Loading";
-import { ModalTerms } from "@/components/ModalPopup";
+import {
+ ModalEvent2024FallDailyAttendance,
+ ModalTerms,
+} from "@/components/ModalPopup";
import Error from "@/pages/Error";
import Navigation from "./Navigation";
@@ -64,6 +67,9 @@ const Skeleton = ({ children }: SkeletonProps) => {
[pathname]
);
+ const [dailyAttendanceOpened, setDailyAttendanceOpened] =
+ useState(false);
+
//#region event2024Fall
useEvent2024FallEffect();
//#endregion
@@ -92,6 +98,11 @@ const Skeleton = ({ children }: SkeletonProps) => {
)}
{children}
+
+
{isDisplayNavigation && (
>;
+ handler: (newValue: Array
) => void;
+};
+
+const DateSection = (props: DateSectionProps) => {
+ return (
+
+
+
+ );
+};
+
+const Event2024FallMissions = () => {
+ const today = getToday();
+
+ const [valueDate, setDate] = useState>>([
+ today.year(),
+ today.month() + 1,
+ today.date(),
+ ]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default memo(Event2024FallMissions);
diff --git a/packages/web/src/pages/Event/Event2024FallMissions.tsx b/packages/web/src/pages/Event/Event2024FallMissions.tsx
index 4955f192..227e4af7 100644
--- a/packages/web/src/pages/Event/Event2024FallMissions.tsx
+++ b/packages/web/src/pages/Event/Event2024FallMissions.tsx
@@ -9,7 +9,7 @@ import CreditAmountStatusContainer from "@/components/Event/CreditAmountStatusCo
import WhiteContainerSuggestJoinEvent from "@/components/Event/WhiteContainerSuggestJoinEvent";
import WhiteContainerSuggestShareEvent from "@/components/Event/WhiteContainerSuggestShareEvent";
import Footer from "@/components/Footer";
-import HeaderWithBackButton from "@/components/Header/HeaderWithBackButton";
+import HeaderWithLeftNav from "@/components/Header/HeaderWithLeftNav";
import WhiteContainer from "@/components/WhiteContainer";
import theme from "@/tools/theme";
@@ -150,9 +150,17 @@ const Event2024FallMissions = () => {
return (
<>
-
- 퀘스트
-
+
diff --git a/packages/web/src/pages/Event/index.tsx b/packages/web/src/pages/Event/index.tsx
index 5b2a39eb..0025e27b 100644
--- a/packages/web/src/pages/Event/index.tsx
+++ b/packages/web/src/pages/Event/index.tsx
@@ -9,6 +9,7 @@ import Event2023FallStore from "./Event2023FallStore";
import Event2023Spring from "./Event2023Spring";
import Event2023SpringGuide from "./Event2023SpringGuide";
import Event2024Fall from "./Event2024Fall";
+import Event2024FallDailyAttendance from "./Event2024FallDailyAttendance";
import Event2024FallHistory from "./Event2024FallHistory";
import Event2024FallLeaderboard from "./Event2024FallLeaderboard";
import Event2024FallMissions from "./Event2024FallMissions";
@@ -53,6 +54,8 @@ const Event = () => {
return ;
case "2024fall-missions":
return ;
+ case "2024fall-daily-attendance":
+ return ;
default:
return ;
}
diff --git a/packages/web/src/static/events/2024fallDailyAttendance.svg b/packages/web/src/static/events/2024fallDailyAttendance.svg
new file mode 100644
index 00000000..12ac9237
--- /dev/null
+++ b/packages/web/src/static/events/2024fallDailyAttendance.svg
@@ -0,0 +1,45 @@
+