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] 종료 미션 보관함 페이지 생성 #510

Merged
merged 11 commits into from
Feb 10, 2024
1 change: 1 addition & 0 deletions src/apis/getQueryKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type QueryList = {
missionStack: {
missionId: string;
};
finishedMissions: undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

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

쿼리키 관련 나중에 이야기 하고 싶은데
여유있을때 같이 이야기 해보시졍~

};

/**
Expand Down
22 changes: 21 additions & 1 deletion src/apis/mission.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import getQueryKey from '@/apis/getQueryKey';
import { type MissionCategory, type MissionItemTypeWithRecordId, type MissionVisibility } from '@/apis/schema/mission';
import {
type FinishedMissionItemType,
type MissionCategory,
type MissionItemTypeWithRecordId,
type MissionVisibility,
} from '@/apis/schema/mission';
import {
useMutation,
type UseMutationOptions,
Expand Down Expand Up @@ -62,6 +67,11 @@ const MISSION_APIS = {
const { data } = await apiInstance.get(`/missions/symbol/${missionId}`);
return data;
},

getFinishedMissions: async (): Promise<GetFinishedMissionsResponse> => {
const { data } = await apiInstance.get<GetFinishedMissionsResponse>('/missions/finished');
return data;
},
};

export default MISSION_APIS;
Expand All @@ -78,6 +88,8 @@ export interface MissionContentType {

type GetMissionsResponse = MissionItemTypeWithRecordId[];

export type GetFinishedMissionsResponse = FinishedMissionItemType[];

interface ModifyMissionResponse {
missionId: string;
name: string;
Expand Down Expand Up @@ -165,3 +177,11 @@ export const useGetMissionStack = (missionId: string, option?: UseQueryOptions<G
...option,
});
};

export const useGetFinishedMissions = (option?: UseQueryOptions<GetFinishedMissionsResponse>) => {
return useQuery<GetFinishedMissionsResponse>({
queryKey: getQueryKey('finishedMissions'),
queryFn: MISSION_APIS.getFinishedMissions,
...option,
});
};
10 changes: 10 additions & 0 deletions src/apis/schema/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ export interface MissionItemTypeWithRecordId extends MissionItemType {
missionRecordId?: string;
}

export interface FinishedMissionItemType {
missionId: number;
name: string;
content: string;
category: MissionCategory;
missionAttainRate: number;
startedAt: string;
finishedAt: string;
}

export enum MissionCategory {
STUDY = 'STUDY',
EXERCISE = 'EXERCISE',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MISSION_CATEGORY_LABEL } from '@/constants/mission';

function MissionHistoryBannerApi({ missionId }: { missionId: string }) {
const { data } = useGetMissionDetailNoSuspense(missionId);
console.log('data: ', data);
Copy link
Collaborator

Choose a reason for hiding this comment

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

콘솔 삭제 부탁드립니다~~


if (!data) return <MissionHistorySkeleton />;

Expand Down
37 changes: 0 additions & 37 deletions src/app/mission/[id]/detail/MissionHistoryTab.tsx

This file was deleted.

32 changes: 6 additions & 26 deletions src/app/mission/[id]/detail/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
'use client';

import { useParams, useRouter } from 'next/navigation';
import { useDeleteMissionMutation } from '@/apis/mission';
import MissionHistoryTab from '@/app/mission/[id]/detail/MissionHistoryTab';
import MissionStartButton from '@/app/mission/[id]/detail/MissionStartButton';
import useCheckCompleteMission from '@/app/mission/[id]/detail/useCheckCompleteMission';
import Dialog from '@/components/Dialog/Dialog';
import Header from '@/components/Header/Header';
import { useSnackBar } from '@/components/SnackBar/SnackBarProvider';
import { MissionDeleteDialog } from '@/components/MissionDetail';
import MissionHistoryTab from '@/components/MissionDetail/MissionHistoryTab';
import Tab from '@/components/Tab/Tab';
import { ROUTER } from '@/constants/router';
import useModal from '@/hooks/useModal';
Expand All @@ -17,18 +15,9 @@ export default function MissionDetailPage() {
const { isOpen, openModal: openDeleteDialog, closeModal: closeDeleteDialog } = useModal();
const router = useRouter();

const { triggerSnackBar } = useSnackBar();
const { id } = useParams();
const { isCompeteMission } = useCheckCompleteMission(id as string);

const { mutate: missionDeleteMutate } = useDeleteMissionMutation(id as string, {
onSuccess: () => {
router.replace(ROUTER.HOME);
},
onError: () => {
triggerSnackBar({ message: '미션 삭제에 실패했습니다. 다시 시도해주세요.' });
},
});
const tabs = [
{
tabName: '미션 내역',
Expand All @@ -44,10 +33,6 @@ export default function MissionDetailPage() {
openDeleteDialog();
};

const handleDeleteCancel = () => {
closeDeleteDialog();
};

return (
<main className={mainWrapperCss}>
<Header
Expand All @@ -63,16 +48,11 @@ export default function MissionDetailPage() {
</div>
<MissionHistoryTab />
<MissionStartButton missionId={id as string} isCompeteMission={isCompeteMission} />
<Dialog
variant={'default'}
<MissionDeleteDialog
isOpen={isOpen}
onClose={closeDeleteDialog}
onConfirm={missionDeleteMutate}
onCancel={handleDeleteCancel}
title="정말 삭제하시겠어요?"
content="미션을 삭제하면 그동안의 기록들이 사라져요."
confirmText="삭제"
cancelText="취소"
closeModal={closeDeleteDialog}
missionId={String(id)}
successRoutePath={ROUTER.HOME}
/>
</main>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/mission/[id]/follow/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import MissionHistoryTab from '@/app/mission/[id]/detail/MissionHistoryTab';
import BottomDim from '@/components/BottomDim/BottomDim';
import Header from '@/components/Header/Header';
import MissionHistoryTab from '@/components/MissionDetail/MissionHistoryTab';
import Tab from '@/components/Tab/Tab';
import { css } from '@styled-system/css';

Expand Down
1 change: 1 addition & 0 deletions src/app/profile/[id]/follows/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import useSearchParamsTypedValue from '@/hooks/useSearchParamsTypedValue';
import { css } from '@/styled-system/css';

type FollowTabType = 'following' | 'follower';

function FollowListPage({ params }: { params: { id: string } }) {
const { searchParams } = useSearchParamsTypedValue<FollowTabType>('tab');
const initTabId = searchParams ?? 'following';
Expand Down
50 changes: 50 additions & 0 deletions src/app/result/FinishedMissionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Link from 'next/link';
import { type GetFinishedMissionsResponse } from '@/apis/mission';
import { type MissionCategory } from '@/apis/schema/mission';
import Badge from '@/components/Badge/Badge';
import { TwoLineListItem } from '@/components/ListItem';
import { MISSION_CATEGORY_LABEL } from '@/constants/mission';
import { ROUTER } from '@/constants/router';
import { flex } from '@/styled-system/patterns';
import { type UseQueryResult } from '@tanstack/react-query';

interface Props {
queryData: UseQueryResult<GetFinishedMissionsResponse>;
}

function FinishedMissionList({ queryData }: Props) {
const { data } = queryData;

const list = data ?? [];

return (
<ul className={ulCss}>
{list.map((item) => {
const category = MISSION_CATEGORY_LABEL[item.category as MissionCategory];
return (
<Link key={item.missionId} href={ROUTER.RESULT.FINISHED_MISSION(item.missionId)}>
<TwoLineListItem
key={item.missionId}
subName={category.label}
name={item.name}
imageUrl={category.imgUrl}
badgeElement={
<Badge color="gray" variant="solid">
{item.missionAttainRate}%
</Badge>
}
/>
</Link>
);
})}
</ul>
);
}

export default FinishedMissionList;

const ulCss = flex({
padding: '16px',
gap: '8px',
flexDirection: 'column',
});
65 changes: 65 additions & 0 deletions src/app/result/OverallStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client';

import { useGetMissionSummary } from '@/apis/mission';
import Character from '@/app/level/guide/Character';
import Banner from '@/components/Banner/Banner';
import LevelStatus from '@/components/LevelStatus/LevelStatus';
import MotionDiv from '@/components/Motion/MotionDiv';
import { css } from '@/styled-system/css';
import { grid } from '@/styled-system/patterns';
import { getLevel } from '@/utils/result';

function OverallStatus() {
const { data, isLoading } = useGetMissionSummary();

const symbolStack = data?.symbolStack ?? 0;
const currentLevel = getLevel(symbolStack);
const totalTime = `${data?.totalMissionHour ?? 0}h ${data?.totalMissionMinute ?? 0}m`;
const totalMissionAttainRate = `${data?.totalMissionAttainRate ?? 0}%`;

return (
<>
{isLoading ? (
// TODO : 스켈레톤 추가
<div></div>
) : (
<>
<MotionDiv variants="fadeInUp" className={imageSectionCss}>
<Character width={280} height={210} level={currentLevel.level} isBackground />
</MotionDiv>
<LevelStatus symbolStack={symbolStack} viewLevel={currentLevel.level} />
<MotionDiv className={bannerSectionCss}>
<Banner
type="card"
description="전체 누적 시간"
iconUrl="/assets/icons/graph/clock.png"
title={totalTime}
/>
<Banner
type="card"
description="총 미션 달성률"
iconUrl="/assets/icons/graph/chart.png"
title={totalMissionAttainRate}
/>
</MotionDiv>
</>
)}
</>
);
}

export default OverallStatus;

const bannerSectionCss = grid({
gridTemplateColumns: '1fr 1fr',
padding: '20px 16px',
gap: '10px',
maxWidth: '376px',
margin: '0 auto',
});

const imageSectionCss = css({
margin: '43px auto 12px',
position: 'relative',
height: '210px',
});
81 changes: 81 additions & 0 deletions src/app/result/finished/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use client';

import { useParams, useRouter } from 'next/navigation';
import MissionHistoryBannerApi from '@/app/mission/[id]/detail/MissionHistoryBanner/MissionHistoryBannerApi';
import { ResultTabId } from '@/app/result/result.constants';
import Header from '@/components/Header/Header';
import { MissionDeleteDialog } from '@/components/MissionDetail';
import MissionCalendar from '@/components/MissionDetail/MissionCalender/MissionCalendar';
import MissionHistoryTabLayout from '@/components/MissionDetail/MissionHistoryTabLayout';
import Tab from '@/components/Tab/Tab';
import { ROUTER } from '@/constants/router';
import useModal from '@/hooks/useModal';
import { css } from '@/styled-system/css';
import dayjs from 'dayjs';

function FinishedMissionDetailPage() {
const { isOpen, openModal: openDeleteDialog, closeModal: closeDeleteDialog } = useModal();
const router = useRouter();

const { id } = useParams();
const missionId = id;
const currentData = dayjs();

const tabs = [
{
tabName: '미션 내역',
id: 'mission-history',
},
];

const handleMenuClick = () => {
openDeleteDialog();
};

return (
<main className={mainWrapperCss}>
<Header
title={'미션 상세'}
rightAction="icon-menu"
iconName={'menu'}
menus={DETAIL_MENUS}
onMenuClick={handleMenuClick}
onBackAction={() => router.replace(ROUTER.RESULT.HOME(ResultTabId.FINISHED_MISSION))}
/>
<div className={tabWrapperCss}>
<Tab tabs={tabs} activeTab={'mission-history'} />
</div>

<MissionHistoryTabLayout>
{/* TODO: 종료 미션 start ~ finish date 표시 */}
{missionId && <MissionHistoryBannerApi missionId={String(missionId)} />}
<MissionCalendar currentData={currentData} missionId={Number(missionId)} />
</MissionHistoryTabLayout>

<MissionDeleteDialog
isOpen={isOpen}
closeModal={closeDeleteDialog}
missionId={String(id)}
successRoutePath={ROUTER.RESULT.HOME(ResultTabId.FINISHED_MISSION)}
/>
</main>
);
}

export default FinishedMissionDetailPage;

const mainWrapperCss = css({
height: '100vh',
width: '100%',
});

const DETAIL_MENUS = [
{
label: '미션 삭제',
id: 'mission-delete',
},
];

const tabWrapperCss = css({
padding: '16px 16px 4px 16px',
});
Loading
Loading