Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 이전 달의 미션 캘린더 정보를 볼 수 있다. #469

Merged
merged 8 commits into from
Feb 5, 2024
52 changes: 40 additions & 12 deletions src/app/mission/[id]/detail/MissionCalender/MissionCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<section>
<div className={missionHistoryCalendarCss}>
{currentYear}년 {currentMonth}월
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '4px',
})}
>
<button type={'button'} className={buttonCss} onClick={onPrevMonth}>
<Icon name="arrow-back" size={12} />
</button>
<div className={missionHistoryCalendarCss}>
{currentYear}년 {currentMonth}월
</div>
{!isCurrentMonth && (
<button type={'button'} className={buttonCss} onClick={onNextMonth}>
<Icon name="arrow-forward" size={12} />
</button>
)}
</div>
<table className={tableCss}>
<thead>
Expand All @@ -53,14 +74,17 @@ function MissionCalendar({
data?.missionRecords || [],
isFollow,
);

const isToday = dayjs().isSame(`${day.year}-${day.month}-${day.date}`, 'day');

return (
<td key={`${day.year}-${day.month}-${day.date}`} className={missionCalendarTdCss}>
{routerLink ? (
<Link href={routerLink}>
<MissionCalendarItem date={day.date} {...restProps} isActive={day.date === selectedDate} />
<MissionCalendarItem date={day.date} {...restProps} isActive={isToday} />
</Link>
) : (
<MissionCalendarItem date={day.date} {...restProps} isActive={day.date === selectedDate} />
<MissionCalendarItem date={day.date} {...restProps} isActive={isToday} />
)}
</td>
);
Expand All @@ -75,6 +99,10 @@ function MissionCalendar({

export default MissionCalendar;

const buttonCss = css({
padding: '8px',
});

const tableCss = css({
width: '100%',
textAlign: 'center',
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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}`;
};
Original file line number Diff line number Diff line change
@@ -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 <MissionHistorySkeleton />;

const title = data.name;
const description = data.content;
Expand Down
13 changes: 4 additions & 9 deletions src/app/mission/[id]/detail/MissionHistoryTab.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={scrollAreaCss}>
<div className={missionHistoryTabCss}>
<Suspense fallback={<MissionHistorySkeleton />}>
{missionId && <MissionHistoryBannerApi missionId={missionId} />}
</Suspense>
<Suspense>
<MissionCalendar isFollow={isFollow} currentDate={currentDate} missionId={Number(missionId)} />
</Suspense>
{missionId && <MissionHistoryBannerApi missionId={missionId} />}
<MissionCalendar isFollow={isFollow} currentData={currentData} missionId={Number(missionId)} />
</div>
</div>
);
Expand Down
89 changes: 89 additions & 0 deletions src/hooks/useCalendar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useState } from 'react';
import useQueryParams from '@/hooks/useQueryParams';
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 } = useQueryParams({ queryKey: 'date' });
const [date, setDate] = useState(queryParams ? dayjs(queryParams) : currentData);
const { monthCalendarData } = getCalenderInfo(date.month() + 1, date.year());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


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;
});
};
21 changes: 21 additions & 0 deletions src/hooks/useQueryParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { usePathname, useRouter, useSearchParams } from 'next/navigation';

/**
* useQueryParams hook - query params를 다루기 위한 hook
* UI 상태를 query params로 관리하기 위한 hook
* @param queryKey
*/
const useQueryParams = <T extends string>({ queryKey }: { queryKey: T }) => {
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();
const queryParams = searchParams.get(queryKey) as string | undefined;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

queryparam 이라는 네이밍을 보고 처음에는 react-query에 관련된 로직이라고 생각했었던거같아요 🤔

Copy link
Collaborator Author

@wade3420 wade3420 Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSearchParamState 로 변경할게요

const setQueryParamsHandler = (newQueryParams: Record<T, string>) => {
router.replace(pathname + '?' + new URLSearchParams(newQueryParams).toString());
};

return { queryParams, setQueryParams: setQueryParamsHandler };
};

export default useQueryParams;
Loading