Skip to content

Commit 6cff739

Browse files
prgmr99skydreamer21Juhyun Kim
authored
feat: 마이페이지 - 회고 상세 보기 (#162)
* feat: 마이페이지 - 완료 작업 세부페이지 라우팅 및 헤더 설정 * fix: 마이페이지 - 회고 데이터 받아오는 api 적용 * feat: 마이페이지 작업 상세 - 문구 및 캐릭터 * feat: 마이페이지 상세 - 페르소나 및 배지 * feat: 마이페이지 상세 - 작업 정보들 * comment: 주석 추가 및 제거 * feat: 마이페이지 상세 - 나의 회고 문구 * feat: 회고 돌아보기 기능 추가 --------- Co-authored-by: skydreamer <skydreamer210@gmail.com> Co-authored-by: Juhyun Kim <juhyun_kim6@tmax.co.kr>
1 parent 64a64b9 commit 6cff739

File tree

11 files changed

+438
-4
lines changed

11 files changed

+438
-4
lines changed

next.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
const nextConfig = {
22
images: {
3-
domains: ["k.kakaocdn.net"], // 카카오 CDN 도메인 추가
3+
domains: ["img1.kakaocdn.net", "k.kakaocdn.net"], // 카카오 CDN 도메인 추가
44
remotePatterns: [
5+
{
6+
protocol: "http",
7+
hostname: "img1.kakaocdn.net",
8+
pathname: "/**",
9+
},
510
{
611
protocol: "http",
712
hostname: "k.kakaocdn.net",

public/icons/mypage/clap.svg

Lines changed: 29 additions & 0 deletions
Loading
81.1 KB
Loading

src/app/(protected)/my-page/_component/TaskContainer.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
1+
import { useExpiredTaskStore } from "@/store/useTaskStore";
12
import type { TaskOrigin } from "@/types/myPage";
23
import { format } from "date-fns";
34
import { ko } from "date-fns/locale";
5+
import { useRouter } from "next/navigation";
46
import React, { useState } from "react";
57

68
const TaskItem = ({ task }: { task: TaskOrigin }) => {
9+
const router = useRouter();
710
const date = new Date(task.dueDatetime);
811
const formattedDate = format(date, "M월 d일 (eee)ㆍa hh:mm까지", {
912
locale: ko,
1013
});
14+
const { setCurrentTask } = useExpiredTaskStore();
15+
16+
const handleTaskClick = () => {
17+
setCurrentTask(task);
18+
router.push(`/my-page/task-detail/${task.id}`);
19+
};
1120

1221
return (
13-
// TODO : 여기에 완료한 일 또는 미룬 일 눌렀을때 해당 화면 전환 함수 추가 필요
14-
<div className="flex flex-col gap-2 py-4">
22+
<div className="flex flex-col gap-2 py-4" onClick={handleTaskClick}>
1523
<div className="text-s2">{task.name}</div>
1624
<div className="text-s3 text-gray-alternative">{formattedDate}</div>
1725
</div>
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
"use client";
2+
3+
import RetrospectItem from "@/app/(protected)/retrospection/[taskId]/_components/RetrospectItem";
4+
import { Badge } from "@/components/component/Badge";
5+
import CustomBackHeader from "@/components/customBackHeader/CustomBackHeader";
6+
import { useUserStore } from "@/store";
7+
import type { TaskWithRetrospection } from "@/types/myPage";
8+
import {
9+
convertIsoToMonthDayTimeText,
10+
formatTimeFromMinutes,
11+
} from "@/utils/dateFormat";
12+
import Image from "next/image";
13+
import { useRouter } from "next/navigation";
14+
15+
type Props = {
16+
task: TaskWithRetrospection;
17+
};
18+
19+
const retrospectItems: RetrospectItems = {
20+
result: {
21+
title: "몰입한 결과에 얼마나 만족하시나요?",
22+
required: true,
23+
},
24+
focus: {
25+
title: "몰입하는 동안 나의 집중력은?",
26+
required: true,
27+
},
28+
keepAndTry: {
29+
title: "이번 몰입의 좋았던 점과 개선할 점은?",
30+
required: false,
31+
},
32+
};
33+
34+
const RESULT_CONTENT = [0, 1, 2, 3, 4];
35+
const FOCUS_STEPS = [0, 1, 2, 3, 4, 5];
36+
const BAR = {
37+
HEIGHT: 18,
38+
SLIDER_RADIUS: 9,
39+
};
40+
41+
export default function ExpiredTaskDetailPage({ task }: Props) {
42+
const router = useRouter();
43+
const { userData } = useUserStore();
44+
45+
const satisfaction = task.satisfaction / 20 - 1;
46+
47+
const taskStatus = task.status === "COMPLETE" ? "COMPLETE" : "FAIL";
48+
49+
const PHRASE = {
50+
COMPLETE: {
51+
topBar: "완료한 일",
52+
main: "잘 완료하셨어요!",
53+
},
54+
FAIL: {
55+
topBar: "미룬 일",
56+
main: "완료를 안하셨네요. 다음엔 꼭 완료해요!",
57+
},
58+
};
59+
60+
const keys = [
61+
{
62+
name: "작은 행동",
63+
onlyComplete: false,
64+
content: task.triggerAction,
65+
},
66+
{
67+
name: "예상 소요시간",
68+
onlyComplete: false,
69+
content: formatTimeFromMinutes(task.estimatedTime),
70+
},
71+
{
72+
name: "마감일",
73+
onlyComplete: false,
74+
content: convertIsoToMonthDayTimeText(task.dueDateTime),
75+
},
76+
];
77+
78+
const filtered = keys.filter((item) => {
79+
if (!item.onlyComplete) {
80+
return item.onlyComplete === false;
81+
}
82+
// onlyComplete === true 인 경우, 조건 함수까지 만족해야 함
83+
return item.onlyComplete === true && taskStatus === "COMPLETE";
84+
});
85+
86+
return (
87+
<div className="flex min-h-screen flex-col pb-[34px] bg-background-primary">
88+
{/* 헤더 부분 */}
89+
<CustomBackHeader
90+
title={PHRASE[taskStatus].topBar}
91+
backRoute="/my-page"
92+
/>
93+
94+
{/* Contents 부분 */}
95+
<div className="mb-8 mt-[54px] flex flex-col gap-5 mx-5 justify-center">
96+
{/* Contents - 작업 개요 */}
97+
<div className="flex flex-col">
98+
{/* Contents - 작업 개요 - 문구*/}
99+
<div className="t3 flex mt-4 mb-5 justify-start">
100+
<p>
101+
{task.name} <br />{" "}
102+
{/* TODO: 이 task.name 이 새로고침 해야 나옴.. 뭔가 고쳐야 함*/}
103+
{PHRASE[taskStatus].main}
104+
</p>
105+
</div>
106+
107+
{/* Contents - 작업 개요 - 작업 정보 */}
108+
<div className="flex flex-col gap-6">
109+
{/* Contents - 작업 개요 - 작업 정보 - 페르소나 */}
110+
<div className="flex flex-col gap-3 justify-center">
111+
<div className="relative flex w-full h-[120px] overflow-visible justify-center items-center">
112+
<div className="absolute top-1/2 -translate-y-1/2 items-center">
113+
<Image
114+
src="/icons/mypage/mypage-character.png"
115+
alt="mypage-character"
116+
width={335}
117+
height={254}
118+
/>
119+
</div>
120+
</div>
121+
<div className="flex w-full justify-center">
122+
<Badge>
123+
{task.personaName} {userData.nickname}
124+
</Badge>
125+
</div>
126+
</div>
127+
128+
{/* Contents - 작업 개요 - 작업 정보 - 페르소나 제외 작업 정보 */}
129+
<div className="flex flex-col gap-4 p-5 bg-component-gray-secondary rounded-[16px]">
130+
{keys.map((item, index) => (
131+
<div
132+
key={index}
133+
className="flex justify-between items-center w-full"
134+
>
135+
<span className="b3 text-gray-alternative">{item.name}</span>
136+
<span className="b3 text-gray-normal">{item.content}</span>
137+
</div>
138+
))}
139+
{/* 완료일 정보는 따로 */}
140+
{taskStatus === "COMPLETE" && (
141+
<div className="flex justify-between items-center w-full">
142+
<span className="b3 text-gray-alternative">완료 일</span>
143+
<div className="inline-flex gap-1 items-center">
144+
<Image
145+
src="/icons/mypage/clap.svg"
146+
alt="mypage-character"
147+
width={23}
148+
height={23}
149+
/>
150+
<span className="b3Bold text-primary">
151+
{" "}
152+
{convertIsoToMonthDayTimeText(task.updatedAt)}
153+
</span>
154+
<Image
155+
src="/icons/mypage/clap.svg"
156+
alt="mypage-character"
157+
width={23}
158+
height={23}
159+
/>
160+
</div>
161+
</div>
162+
)}
163+
</div>
164+
</div>
165+
</div>
166+
167+
{/* Contents - 작업 회고 내용 */}
168+
<div className="flex flex-col">
169+
{/* Contents - 작업 회고 내용 - 제목 문구 */}
170+
<div>
171+
<div className="t3 flex my-3 justify-start">
172+
<p>나의 회고</p>
173+
</div>
174+
</div>
175+
176+
{/* Contents - 작업 회고 내용 - 회고 내용 */}
177+
<div className="flex flex-col gap-5">
178+
{" "}
179+
{/* 실제 유저 회고 부분 */}
180+
{/* 몰입 결과 회고 */}
181+
<RetrospectItem
182+
title={retrospectItems.result.title}
183+
required={retrospectItems.result.required}
184+
>
185+
<div className="flex gap-[18px]">
186+
{RESULT_CONTENT.map((num, index) => (
187+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
188+
<div key={index}>
189+
<Image
190+
src={`/icons/retro/retro1-${num}-${satisfaction === num ? 1 : 0}.svg`}
191+
alt="retro content index"
192+
width={40}
193+
height={40}
194+
priority
195+
/>
196+
</div>
197+
))}
198+
</div>
199+
</RetrospectItem>
200+
{/* 몰입하는 동안 나의 집중력 */}
201+
<RetrospectItem
202+
title={retrospectItems.focus.title}
203+
required={retrospectItems.focus.required}
204+
>
205+
<div className="w-full mx-2 mt-1">
206+
<div
207+
className="relative flex items-center"
208+
style={{
209+
height: `${BAR.HEIGHT}px`,
210+
}}
211+
>
212+
{/* 전체 바 배경 */}
213+
<div
214+
className="absolute rounded-full bg-line-tertiary"
215+
style={{
216+
height: `${BAR.HEIGHT}px`,
217+
width: `calc(100% + ${BAR.SLIDER_RADIUS * 2}px)`, // 16px 양쪽 추가
218+
left: `-${BAR.SLIDER_RADIUS}px`, // 왼쪽으로 16px 이동
219+
}}
220+
/>
221+
222+
{/* 선택된 채워진 부분 */}
223+
<div
224+
className="absolute rounded-full bg-gradient-to-r from-blue-200 to-purple-200 transition-all duration-200"
225+
style={{
226+
height: `${BAR.HEIGHT}px`,
227+
width: `calc(${task.concentration}% + ${BAR.SLIDER_RADIUS * 2}px)`,
228+
left: `-${BAR.SLIDER_RADIUS}px`,
229+
}}
230+
/>
231+
232+
{/* 점들 */}
233+
<div className="relative z-10 flex justify-between w-full">
234+
{FOCUS_STEPS.map((step, i) => (
235+
<div
236+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
237+
key={i}
238+
className={`w-[6px] h-[6px] rounded-full transition-all duration-200 ${
239+
task.concentration >= step
240+
? "bg-background-skyblue opacity-90"
241+
: "bg-background-skyblue opacity-30"
242+
}`}
243+
/>
244+
))}
245+
</div>
246+
247+
{/* 슬라이더 핸들 */}
248+
<div
249+
className="absolute m-3 z-30 rounded-full border-2 border-white bg-white shadow"
250+
style={{
251+
width: `${BAR.SLIDER_RADIUS * 2}px`,
252+
height: `${BAR.SLIDER_RADIUS * 2}px`,
253+
left: `calc(${(task.concentration / 5) * 100}% - ${BAR.SLIDER_RADIUS * 2}px)`,
254+
transition: "left 0.2s ease",
255+
}}
256+
/>
257+
</div>
258+
259+
{/* 아래 숫자 레이블 */}
260+
<div className="mt-1.5 flex justify-between c3 text-gray-alternative font-medium">
261+
{FOCUS_STEPS.map((step, i) => (
262+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
263+
<div key={i} className="w-[6px] flex justify-center">
264+
<span
265+
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
266+
key={i}
267+
className={
268+
task.concentration === step
269+
? "text-gray-alternative"
270+
: ""
271+
}
272+
>
273+
{`${step * 20}`}
274+
</span>
275+
</div>
276+
))}
277+
</div>
278+
</div>
279+
</RetrospectItem>
280+
{/* 몰입 회고 텍스트 */}
281+
<RetrospectItem
282+
title={retrospectItems.keepAndTry.title}
283+
required={retrospectItems.keepAndTry.required}
284+
>
285+
<div className="flex flex-col w-full gap-3 px-4 py-3 bg-component-gray-tertiary rounded-[11.25px]">
286+
<textarea
287+
value={task.comment}
288+
placeholder="좋았던 점과 개선할 점을 간단히 작성해주세요."
289+
className="w-full h-20 bg-component-gray-tertiary b3 text-gray-normal placeholder-text-gray-normal
290+
resize-none focus:outline-none"
291+
/>
292+
</div>
293+
</RetrospectItem>
294+
</div>
295+
</div>
296+
</div>
297+
</div>
298+
);
299+
}

0 commit comments

Comments
 (0)