Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
const nextConfig = {
images: {
domains: ["k.kakaocdn.net"], // 카카오 CDN 도메인 추가
domains: ["img1.kakaocdn.net", "k.kakaocdn.net"], // 카카오 CDN 도메인 추가
remotePatterns: [
{
protocol: "http",
hostname: "img1.kakaocdn.net",
pathname: "/**",
},
{
protocol: "http",
hostname: "k.kakaocdn.net",
Expand Down
29 changes: 29 additions & 0 deletions public/icons/mypage/clap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/icons/mypage/mypage-character.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions src/app/(protected)/my-page/_component/TaskContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { useExpiredTaskStore } from "@/store/useTaskStore";
import type { TaskOrigin } from "@/types/myPage";
import { format } from "date-fns";
import { ko } from "date-fns/locale";
import { useRouter } from "next/navigation";
import React, { useState } from "react";

const TaskItem = ({ task }: { task: TaskOrigin }) => {
const router = useRouter();
const date = new Date(task.dueDatetime);
const formattedDate = format(date, "M월 d일 (eee)ㆍa hh:mm까지", {
locale: ko,
});
const { setCurrentTask } = useExpiredTaskStore();

const handleTaskClick = () => {
setCurrentTask(task);
router.push(`/my-page/task-detail/${task.id}`);
};

return (
// TODO : 여기에 완료한 일 또는 미룬 일 눌렀을때 해당 화면 전환 함수 추가 필요
<div className="flex flex-col gap-2 py-4">
<div className="flex flex-col gap-2 py-4" onClick={handleTaskClick}>
<div className="text-s2">{task.name}</div>
<div className="text-s3 text-gray-alternative">{formattedDate}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
"use client";

import RetrospectItem from "@/app/(protected)/retrospection/[taskId]/_components/RetrospectItem";
import { Badge } from "@/components/component/Badge";
import CustomBackHeader from "@/components/customBackHeader/CustomBackHeader";
import { useUserStore } from "@/store";
import type { TaskWithRetrospection } from "@/types/myPage";
import {
convertIsoToMonthDayTimeText,
formatTimeFromMinutes,
} from "@/utils/dateFormat";
import Image from "next/image";
import { useRouter } from "next/navigation";

type Props = {
task: TaskWithRetrospection;
};

const retrospectItems: RetrospectItems = {
result: {
title: "몰입한 결과에 얼마나 만족하시나요?",
required: true,
},
focus: {
title: "몰입하는 동안 나의 집중력은?",
required: true,
},
keepAndTry: {
title: "이번 몰입의 좋았던 점과 개선할 점은?",
required: false,
},
};

const RESULT_CONTENT = [0, 1, 2, 3, 4];
const FOCUS_STEPS = [0, 1, 2, 3, 4, 5];
const BAR = {
HEIGHT: 18,
SLIDER_RADIUS: 9,
};

export default function ExpiredTaskDetailPage({ task }: Props) {
const router = useRouter();
const { userData } = useUserStore();

const satisfaction = task.satisfaction / 20 - 1;

const taskStatus = task.status === "COMPLETE" ? "COMPLETE" : "FAIL";

const PHRASE = {
COMPLETE: {
topBar: "완료한 일",
main: "잘 완료하셨어요!",
},
FAIL: {
topBar: "미룬 일",
main: "완료를 안하셨네요. 다음엔 꼭 완료해요!",
},
};

const keys = [
{
name: "작은 행동",
onlyComplete: false,
content: task.triggerAction,
},
{
name: "예상 소요시간",
onlyComplete: false,
content: formatTimeFromMinutes(task.estimatedTime),
},
{
name: "마감일",
onlyComplete: false,
content: convertIsoToMonthDayTimeText(task.dueDateTime),
},
];

const filtered = keys.filter((item) => {
if (!item.onlyComplete) {
return item.onlyComplete === false;
}
// onlyComplete === true 인 경우, 조건 함수까지 만족해야 함
return item.onlyComplete === true && taskStatus === "COMPLETE";
});

return (
<div className="flex min-h-screen flex-col pb-[34px] bg-background-primary">
{/* 헤더 부분 */}
<CustomBackHeader
title={PHRASE[taskStatus].topBar}
backRoute="/my-page"
/>

{/* Contents 부분 */}
<div className="mb-8 mt-[54px] flex flex-col gap-5 mx-5 justify-center">
{/* Contents - 작업 개요 */}
<div className="flex flex-col">
{/* Contents - 작업 개요 - 문구*/}
<div className="t3 flex mt-4 mb-5 justify-start">
<p>
{task.name} <br />{" "}
{/* TODO: 이 task.name 이 새로고침 해야 나옴.. 뭔가 고쳐야 함*/}
{PHRASE[taskStatus].main}
</p>
</div>

{/* Contents - 작업 개요 - 작업 정보 */}
<div className="flex flex-col gap-6">
{/* Contents - 작업 개요 - 작업 정보 - 페르소나 */}
<div className="flex flex-col gap-3 justify-center">
<div className="relative flex w-full h-[120px] overflow-visible justify-center items-center">
<div className="absolute top-1/2 -translate-y-1/2 items-center">
<Image
src="/icons/mypage/mypage-character.png"
alt="mypage-character"
width={335}
height={254}
/>
</div>
</div>
<div className="flex w-full justify-center">
<Badge>
{task.personaName} {userData.nickname}
</Badge>
</div>
</div>

{/* Contents - 작업 개요 - 작업 정보 - 페르소나 제외 작업 정보 */}
<div className="flex flex-col gap-4 p-5 bg-component-gray-secondary rounded-[16px]">
{keys.map((item, index) => (
<div
key={index}
className="flex justify-between items-center w-full"
>
<span className="b3 text-gray-alternative">{item.name}</span>
<span className="b3 text-gray-normal">{item.content}</span>
</div>
))}
{/* 완료일 정보는 따로 */}
{taskStatus === "COMPLETE" && (
<div className="flex justify-between items-center w-full">
<span className="b3 text-gray-alternative">완료 일</span>
<div className="inline-flex gap-1 items-center">
<Image
src="/icons/mypage/clap.svg"
alt="mypage-character"
width={23}
height={23}
/>
<span className="b3Bold text-primary">
{" "}
{convertIsoToMonthDayTimeText(task.updatedAt)}
</span>
<Image
src="/icons/mypage/clap.svg"
alt="mypage-character"
width={23}
height={23}
/>
</div>
</div>
)}
</div>
</div>
</div>

{/* Contents - 작업 회고 내용 */}
<div className="flex flex-col">
{/* Contents - 작업 회고 내용 - 제목 문구 */}
<div>
<div className="t3 flex my-3 justify-start">
<p>나의 회고</p>
</div>
</div>

{/* Contents - 작업 회고 내용 - 회고 내용 */}
<div className="flex flex-col gap-5">
{" "}
{/* 실제 유저 회고 부분 */}
{/* 몰입 결과 회고 */}
<RetrospectItem
title={retrospectItems.result.title}
required={retrospectItems.result.required}
>
<div className="flex gap-[18px]">
{RESULT_CONTENT.map((num, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<div key={index}>
<Image
src={`/icons/retro/retro1-${num}-${satisfaction === num ? 1 : 0}.svg`}
alt="retro content index"
width={40}
height={40}
priority
/>
</div>
))}
</div>
</RetrospectItem>
{/* 몰입하는 동안 나의 집중력 */}
<RetrospectItem
title={retrospectItems.focus.title}
required={retrospectItems.focus.required}
>
<div className="w-full mx-2 mt-1">
<div
className="relative flex items-center"
style={{
height: `${BAR.HEIGHT}px`,
}}
>
{/* 전체 바 배경 */}
<div
className="absolute rounded-full bg-line-tertiary"
style={{
height: `${BAR.HEIGHT}px`,
width: `calc(100% + ${BAR.SLIDER_RADIUS * 2}px)`, // 16px 양쪽 추가
left: `-${BAR.SLIDER_RADIUS}px`, // 왼쪽으로 16px 이동
}}
/>

{/* 선택된 채워진 부분 */}
<div
className="absolute rounded-full bg-gradient-to-r from-blue-200 to-purple-200 transition-all duration-200"
style={{
height: `${BAR.HEIGHT}px`,
width: `calc(${task.concentration}% + ${BAR.SLIDER_RADIUS * 2}px)`,
left: `-${BAR.SLIDER_RADIUS}px`,
}}
/>

{/* 점들 */}
<div className="relative z-10 flex justify-between w-full">
{FOCUS_STEPS.map((step, i) => (
<div
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
key={i}
className={`w-[6px] h-[6px] rounded-full transition-all duration-200 ${
task.concentration >= step
? "bg-background-skyblue opacity-90"
: "bg-background-skyblue opacity-30"
}`}
/>
))}
</div>

{/* 슬라이더 핸들 */}
<div
className="absolute m-3 z-30 rounded-full border-2 border-white bg-white shadow"
style={{
width: `${BAR.SLIDER_RADIUS * 2}px`,
height: `${BAR.SLIDER_RADIUS * 2}px`,
left: `calc(${(task.concentration / 5) * 100}% - ${BAR.SLIDER_RADIUS * 2}px)`,
transition: "left 0.2s ease",
}}
/>
</div>

{/* 아래 숫자 레이블 */}
<div className="mt-1.5 flex justify-between c3 text-gray-alternative font-medium">
{FOCUS_STEPS.map((step, i) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<div key={i} className="w-[6px] flex justify-center">
<span
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
key={i}
className={
task.concentration === step
? "text-gray-alternative"
: ""
}
>
{`${step * 20}`}
</span>
</div>
))}
</div>
</div>
</RetrospectItem>
{/* 몰입 회고 텍스트 */}
<RetrospectItem
title={retrospectItems.keepAndTry.title}
required={retrospectItems.keepAndTry.required}
>
<div className="flex flex-col w-full gap-3 px-4 py-3 bg-component-gray-tertiary rounded-[11.25px]">
<textarea
value={task.comment}
placeholder="좋았던 점과 개선할 점을 간단히 작성해주세요."
className="w-full h-20 bg-component-gray-tertiary b3 text-gray-normal placeholder-text-gray-normal
resize-none focus:outline-none"
/>
</div>
</RetrospectItem>
</div>
</div>
</div>
</div>
);
}
Loading