diff --git a/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.tsx b/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.tsx index 4ef821b6..4906f0a0 100644 --- a/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.tsx +++ b/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.tsx @@ -2,37 +2,58 @@ import Link from 'next/link'; import { useGetRecord } from '@/apis'; import { WEEK_DAYS } from '@/app/mission/[id]/detail/MissionCalender/MissionCalendar.constants'; import { - getCalenderInfo, getMissionCalendarItemProps, getYearMonth, } from '@/app/mission/[id]/detail/MissionCalender/MissionCalendar.utils'; import MissionCalendarItem from '@/app/mission/[id]/detail/MissionCalender/MissionCalendarItem'; +import Icon from '@/components/Icon'; +import useCalendar from '@/hooks/useCalendar'; import { css } from '@styled-system/css'; +import dayjs, { type Dayjs } from 'dayjs'; function MissionCalendar({ - currentDate, + currentData, missionId, isFollow, }: { - currentDate: Date; + currentData: Dayjs; missionId: number; isFollow?: boolean; }) { - const currentYear = currentDate.getFullYear(); - const currentMonth = currentDate.getMonth() + 1; - const selectedDate = currentDate.getDate(); + const { date, monthCalendarData, onPrevMonth, onNextMonth, isCurrentMonth } = useCalendar({ + currentData, + isQueryParams: true, + }); + + const currentYear = date.year(); + const currentMonth = date.month() + 1; - const { monthCalendarData } = getCalenderInfo(currentMonth, currentYear); const { data } = useGetRecord({ missionId, - yearMonth: getYearMonth(currentDate), + yearMonth: getYearMonth(date), }); const missionStartedAt = data?.missionStartedAt || ''; return (
-
- {currentYear}년 {currentMonth}월 +
+ +
+ {currentYear}년 {currentMonth}월 +
+ {!isCurrentMonth && ( + + )}
@@ -53,14 +74,17 @@ function MissionCalendar({ data?.missionRecords || [], isFollow, ); + + const isToday = dayjs().isSame(`${day.year}-${day.month}-${day.date}`, 'day'); + return ( ); @@ -75,6 +99,10 @@ function MissionCalendar({ export default MissionCalendar; +const buttonCss = css({ + padding: '8px', +}); + const tableCss = css({ width: '100%', textAlign: 'center', diff --git a/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.utils.ts b/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.utils.ts index b8c48911..c4bbae14 100644 --- a/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.utils.ts +++ b/src/app/mission/[id]/detail/MissionCalender/MissionCalendar.utils.ts @@ -1,58 +1,6 @@ import { type RecordType } from '@/apis/schema/record'; import { ROUTER } from '@/constants/router'; -import dayjs from 'dayjs'; - -const getWeekArray = (totalDate: number, offsetDate: number) => { - return Array.from({ length: 7 }, (_, i) => { - const currentDate = offsetDate + i; - if (currentDate < 0) { - return 0; - } - - if (currentDate > totalDate) { - return 0; - } - return currentDate; - }); -}; - -const getMonthArray = (prevBlankCount: number, totalDate: number) => { - const weekNumber = Math.ceil((prevBlankCount + totalDate) / 7); - - return Array.from({ length: weekNumber }, (_, weekIndex) => { - const offsetDate = weekIndex * 7 - prevBlankCount + 1; - - return getWeekArray(totalDate, offsetDate); - }); -}; - -export const getCalenderInfo = (currentMonth: number, currentYear: number) => { - // 이번달 1일 - const firstDate = new Date(currentYear, currentMonth - 1, 1); - - // 이번달 마지막날 - const lastDate = new Date(currentYear, currentMonth, 0); - - // getDay() : 일요일 0 ~ 토요일 6 - const prevBlankCount = firstDate.getDay(); - const totalDate = lastDate.getDate(); - - const monthCalendarData = getMonthArray(prevBlankCount, totalDate).map((week) => - week.map((date) => { - if (date === 0) { - return null; - } - - return { - year: currentYear, - month: currentMonth, - date, - }; - }), - ); - - return { monthCalendarData }; -}; +import dayjs, { type Dayjs } from 'dayjs'; export const getMissionCalendarItemProps = ( missionStartAt: string, @@ -95,9 +43,9 @@ export const getMissionCalendarItemProps = ( }; }; -export const getYearMonth = (currentDate: Date) => { - const year = currentDate.getFullYear(); - const month = currentDate.getMonth() + 1; +export const getYearMonth = (currentDate: Dayjs) => { + const year = currentDate.year(); + const month = currentDate.month() + 1; const monthString = month < 10 ? `0${month}` : `${month}`; return `${year}-${monthString}`; }; diff --git a/src/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBannerApi.tsx b/src/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBannerApi.tsx index 29610576..2886257e 100644 --- a/src/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBannerApi.tsx +++ b/src/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBannerApi.tsx @@ -1,9 +1,12 @@ -import { useGetMissionDetail } from '@/apis/mission'; +import { useGetMissionDetailNoSuspense } from '@/apis/mission'; import MissionHistoryBanner from '@/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBanner'; +import MissionHistorySkeleton from '@/app/mission/[id]/detail/MissionHistoryBanner/MissionHistorySkeleton'; import { MISSION_CATEGORY_LABEL } from '@/constants/mission'; function MissionHistoryBannerApi({ missionId }: { missionId: string }) { - const { data } = useGetMissionDetail(missionId); + const { data } = useGetMissionDetailNoSuspense(missionId); + + if (!data) return ; const title = data.name; const description = data.content; diff --git a/src/app/mission/[id]/detail/MissionHistoryTab.tsx b/src/app/mission/[id]/detail/MissionHistoryTab.tsx index 87a5db61..0ad14a88 100644 --- a/src/app/mission/[id]/detail/MissionHistoryTab.tsx +++ b/src/app/mission/[id]/detail/MissionHistoryTab.tsx @@ -1,24 +1,19 @@ -import { Suspense } from 'react'; import { useParams } from 'next/navigation'; import MissionCalendar from '@/app/mission/[id]/detail/MissionCalender/MissionCalendar'; import MissionHistoryBannerApi from '@/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBannerApi'; -import MissionHistorySkeleton from '@/app/mission/[id]/detail/MissionHistoryBanner/MissionHistorySkeleton'; import { css } from '@styled-system/css'; +import dayjs from 'dayjs'; function MissionHistoryTab({ isFollow }: { isFollow?: boolean }) { const { id } = useParams(); const missionId = id as string; - const currentDate = new Date(); + const currentData = dayjs(); return (
- }> - {missionId && } - - - - + {missionId && } +
); diff --git a/src/hooks/useCalendar.ts b/src/hooks/useCalendar.ts new file mode 100644 index 00000000..0bf6f946 --- /dev/null +++ b/src/hooks/useCalendar.ts @@ -0,0 +1,89 @@ +import { useState } from 'react'; +import useSearchParamState from '@/hooks/useSearchParamState'; +import dayjs, { type Dayjs } from 'dayjs'; + +/** + * useCalendar hook - 달력 정보를 다루기 위한 hook + * + * @param date 초기 랜더링 시 달력의 기준이 되는 날짜 + * @param isQueryParams 년과 월의 정보를 query params로 관리할지 여부 + */ +const useCalendar = ({ currentData, isQueryParams }: { currentData: Dayjs; isQueryParams?: boolean }) => { + const { queryParams, setQueryParams } = useSearchParamState({ queryKey: 'date' }); + const [date, setDate] = useState(queryParams ? dayjs(queryParams) : currentData); + const { monthCalendarData } = getCalenderInfo(date.month() + 1, date.year()); + + const onNextMonth = () => { + const nextMonth = date.add(1, 'month'); + setDate(nextMonth); + if (isQueryParams) { + setQueryParams({ date: nextMonth.format('YYYY-MM') }); + } + }; + + const onPrevMonth = () => { + const prevMonth = date.subtract(1, 'month'); + setDate(prevMonth); + if (isQueryParams) { + setQueryParams({ date: prevMonth.format('YYYY-MM') }); + } + }; + + const isCurrentMonth = currentData.month() === date.month() && currentData.year() === date.year(); + + return { date, monthCalendarData, onNextMonth, onPrevMonth, isCurrentMonth }; +}; + +export default useCalendar; + +const getCalenderInfo = (currentMonth: number, currentYear: number) => { + // 이번달 1일 + const firstDate = new Date(currentYear, currentMonth - 1, 1); + + // 이번달 마지막날 + const lastDate = new Date(currentYear, currentMonth, 0); + + // getDay() : 일요일 0 ~ 토요일 6 + const prevBlankCount = firstDate.getDay(); + const totalDate = lastDate.getDate(); + + const monthCalendarData = getMonthArray(prevBlankCount, totalDate).map((week) => + week.map((date) => { + if (date === 0) { + return null; + } + + return { + year: currentYear, + month: currentMonth, + date, + }; + }), + ); + + return { monthCalendarData }; +}; + +const getMonthArray = (prevBlankCount: number, totalDate: number) => { + const weekNumber = Math.ceil((prevBlankCount + totalDate) / 7); + + return Array.from({ length: weekNumber }, (_, weekIndex) => { + const offsetDate = weekIndex * 7 - prevBlankCount + 1; + + return getWeekArray(totalDate, offsetDate); + }); +}; + +const getWeekArray = (totalDate: number, offsetDate: number) => { + return Array.from({ length: 7 }, (_, i) => { + const currentDate = offsetDate + i; + if (currentDate < 0) { + return 0; + } + + if (currentDate > totalDate) { + return 0; + } + return currentDate; + }); +}; diff --git a/src/hooks/useSearchParamState.ts b/src/hooks/useSearchParamState.ts new file mode 100644 index 00000000..31c907d0 --- /dev/null +++ b/src/hooks/useSearchParamState.ts @@ -0,0 +1,21 @@ +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; + +/** + * useSearchParamState hook - query params를 다루기 위한 hook + * UI 상태를 query params로 관리하기 위한 hook + * @param queryKey + */ +const useSearchParamState = ({ queryKey }: { queryKey: T }) => { + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const queryParams = searchParams.get(queryKey) as string | undefined; + + const setQueryParamsHandler = (newQueryParams: Record) => { + router.replace(pathname + '?' + new URLSearchParams(newQueryParams).toString()); + }; + + return { queryParams, setQueryParams: setQueryParamsHandler }; +}; + +export default useSearchParamState;
{routerLink ? ( - + ) : ( - + )}