Skip to content

Commit e61d898

Browse files
authored
refactor: Enhance home page (#156)
* refactor: separate header * refactor: create task status custom hook * refactor: separate footer component * refactor: make all task tab as a component * refactor: Header memoization * refactor: Footer memoization * refactor: separate main section as components * refactor: props setting * refactor: InProgressTaskItem refactoring * refactor: dynamic routing applied
1 parent 7327618 commit e61d898

File tree

38 files changed

+2061
-1325
lines changed

38 files changed

+2061
-1325
lines changed

src/app/(protected)/(root)/_components/InProgressTaskItem.tsx

Lines changed: 43 additions & 461 deletions
Large diffs are not rendered by default.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Image from "next/image";
2+
import { memo } from "react";
3+
4+
interface NoTaskScreenProps {
5+
firstText: string;
6+
secondText: string;
7+
thirdText: string;
8+
}
9+
10+
const NoTaskScreen = ({
11+
firstText,
12+
secondText,
13+
thirdText,
14+
}: NoTaskScreenProps) => {
15+
return (
16+
<div className="mb-[40px]">
17+
<div className="flex flex-col items-center justify-center">
18+
<Image
19+
src="/icons/home/xman.svg"
20+
alt="Character"
21+
width={80}
22+
height={80}
23+
className="mb-[48px] mt-[60px]"
24+
priority
25+
/>
26+
<h2 className="t3 text-center text-text-strong">{firstText}</h2>
27+
<h2 className="t3 mb-2 text-center text-text-strong">{secondText}</h2>
28+
<p className="b3 text-center text-text-alternative">{thirdText}</p>
29+
</div>
30+
</div>
31+
);
32+
};
33+
34+
export default memo(NoTaskScreen);

src/app/(protected)/(root)/_components/TaskItem.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { useResetAlerts } from "@/hooks/useTasks";
34
import type { TaskStatus } from "@/types/task";
45
import {
56
calculateRemainingTime,
@@ -9,7 +10,7 @@ import {
910
import Image from "next/image";
1011
import { useRouter } from "next/navigation";
1112
import type React from "react";
12-
import { useCallback, useEffect, useState } from "react";
13+
import { memo, useCallback, useEffect, useState } from "react";
1314

1415
type TaskItemProps = {
1516
title: string;
@@ -21,7 +22,6 @@ type TaskItemProps = {
2122
onPreviewStart?: (taskId?: number) => void;
2223
ignoredAlerts?: number;
2324
timeRequired: string;
24-
resetAlerts?: (taskId: number) => void;
2525
dueDatetime?: string;
2626
status?: TaskStatus;
2727
};
@@ -36,7 +36,6 @@ const TaskItem: React.FC<TaskItemProps> = ({
3636
onDelete,
3737
onPreviewStart = () => {},
3838
ignoredAlerts = 0,
39-
resetAlerts = () => {},
4039
dueDatetime,
4140
status,
4241
}) => {
@@ -46,6 +45,8 @@ const TaskItem: React.FC<TaskItemProps> = ({
4645
const [remainingTime, setRemainingTime] = useState<string>("");
4746
const [isUrgent, setIsUrgent] = useState<boolean>(false);
4847

48+
const resetAlerts = useResetAlerts();
49+
4950
// 진행 중인 태스크인지 확인
5051
const isInProgress = status === "inProgress";
5152

@@ -281,4 +282,4 @@ const TaskItem: React.FC<TaskItemProps> = ({
281282
);
282283
};
283284

284-
export default TaskItem;
285+
export default memo(TaskItem);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { Task } from "@/types/task";
2+
import Image from "next/image";
3+
import React from "react";
4+
import AllTasksTab from "./allTasksTab/AllTasksTab";
5+
6+
interface AllTaskTabWrapperProps {
7+
isAllEmpty: boolean;
8+
inProgressTasks: Task[];
9+
todayTasks: Task[];
10+
weeklyTasks: Task[];
11+
futureTasks: Task[];
12+
onTaskClick: (task: Task) => void;
13+
onDeleteTask: (taskId: number) => void;
14+
}
15+
16+
const AllTaskTabWrapper = ({
17+
isAllEmpty,
18+
inProgressTasks,
19+
todayTasks,
20+
weeklyTasks,
21+
futureTasks,
22+
onTaskClick,
23+
onDeleteTask,
24+
}: AllTaskTabWrapperProps) => {
25+
return isAllEmpty ? (
26+
<div className="mt-[130px]">
27+
<div className="flex h-full flex-col items-center justify-center px-4 text-center">
28+
<div className="mb-[40px]">
29+
<Image
30+
src="/icons/home/rocket.svg"
31+
alt="Rocket"
32+
width={142}
33+
height={80}
34+
className="mx-auto"
35+
priority
36+
/>
37+
</div>
38+
<h2 className="t3 mb-[8px] text-text-strong">
39+
이번주 할일이 없어요.
40+
<br />
41+
마감할 일을 추가해볼까요?
42+
</h2>
43+
<p className="b3 text-text-alternative">
44+
미루지 않도록 알림을 보내 챙겨드릴게요.
45+
</p>
46+
</div>
47+
</div>
48+
) : (
49+
<AllTasksTab
50+
inProgressTasks={inProgressTasks}
51+
todayTasks={todayTasks}
52+
weeklyTasks={weeklyTasks}
53+
futureTasks={futureTasks}
54+
onTaskClick={onTaskClick}
55+
onDeleteTask={onDeleteTask}
56+
/>
57+
);
58+
};
59+
60+
export default AllTaskTabWrapper;

src/app/(protected)/(root)/_components/AllTasksTab.tsx renamed to src/app/(protected)/(root)/_components/allTaskTabWrapper/allTasksTab/AllTasksTab.tsx

File renamed without changes.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// TODO(prgmr99): Drawer 적용해야 함
2+
3+
import type { Task } from "@/types/task";
4+
import { memo } from "react";
5+
6+
interface ExpiredTaskDrawerProps {
7+
expiredTask: Task;
8+
handleGoToReflection: (taskId: number) => void;
9+
handleCloseExpiredSheet: () => void;
10+
}
11+
12+
const ExpiredTaskDrawer = ({
13+
expiredTask,
14+
handleGoToReflection,
15+
handleCloseExpiredSheet,
16+
}: ExpiredTaskDrawerProps) => {
17+
const { id, title, dueDate, dueDay, dueTime } = expiredTask;
18+
19+
return (
20+
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black bg-opacity-60">
21+
<div className="flex w-full flex-col items-center rounded-t-[28px] bg-component-gray-secondary p-4 pt-10">
22+
<h2 className="t3 text-center text-text-strong">{title}</h2>
23+
<p className="t3 mb-2 text-center text-text-strong">
24+
작업이 끝났어요. 짧게 돌아볼까요?
25+
</p>
26+
<div className="flex w-full justify-between">
27+
<p className="b3 mb-7 text-text-neutral">마감일 </p>
28+
<p className="b3 mb-7 text-text-neutral">
29+
{new Date(dueDate).toLocaleDateString("ko-KR", {
30+
month: "long",
31+
day: "numeric",
32+
})}
33+
({dueDay}), {dueTime}
34+
</p>
35+
</div>
36+
<button
37+
type="button"
38+
className="l2 mb-3 w-full rounded-[16px] bg-component-accent-primary py-4 text-white"
39+
onClick={() => handleGoToReflection(id)}
40+
>
41+
회고하기
42+
</button>
43+
44+
<button
45+
type="button"
46+
className="l2 w-full py-4 text-text-neutral"
47+
onClick={handleCloseExpiredSheet}
48+
>
49+
닫기
50+
</button>
51+
</div>
52+
</div>
53+
);
54+
};
55+
56+
export default memo(ExpiredTaskDrawer);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { memo, useEffect, useState } from "react";
2+
import TaskAddButton from "./taskAddButton/TaskAddButton";
3+
4+
const Footer = ({ onClick }: { onClick: () => void }) => {
5+
const [showTooltip, setShowTooltip] = useState(true);
6+
7+
useEffect(() => {
8+
const hasVisited = localStorage.getItem("hasVisitedBefore");
9+
10+
if (hasVisited) {
11+
setShowTooltip(false);
12+
} else {
13+
localStorage.setItem("hasVisitedBefore", "true");
14+
}
15+
}, []);
16+
17+
return (
18+
<footer className="fixed bottom-0 left-0 right-0 z-10">
19+
<div
20+
className="pointer-events-none absolute bottom-0 left-0 right-0 h-40"
21+
style={{
22+
background:
23+
"linear-gradient(to bottom, rgba(15, 17, 20, 0) 0%, rgba(15, 17, 20, 1) 100%)",
24+
}}
25+
/>
26+
27+
<TaskAddButton showTooltip={showTooltip} onClick={onClick} />
28+
</footer>
29+
);
30+
};
31+
32+
export default memo(Footer);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Button } from "@/components/ui/button";
2+
import Image from "next/image";
3+
import React, { memo } from "react";
4+
5+
const TaskAddButton = ({
6+
showTooltip,
7+
onClick,
8+
}: { showTooltip: boolean; onClick: () => void }) => {
9+
return (
10+
<div className="relative flex justify-end p-5 pb-[47px]">
11+
{showTooltip && (
12+
<div className="b3 absolute bottom-[130px] right-4 rounded-[12px] bg-component-accent-primary px-4 py-3 text-text-strong shadow-lg">
13+
지금 바로 할 일을 추가해보세요!
14+
<div
15+
className="absolute h-0 w-0"
16+
style={{
17+
bottom: "-11px",
18+
right: "3rem",
19+
transform: "translateX(50%)",
20+
borderStyle: "solid",
21+
borderWidth: "12px 7px 0 7px",
22+
borderColor: "#6B6BE1 transparent transparent transparent",
23+
}}
24+
/>
25+
</div>
26+
)}
27+
<Button
28+
variant="point"
29+
size="md"
30+
className="l2 flex h-[52px] w-[130px] items-center gap-2 rounded-full py-[16.5px] text-text-inverse"
31+
onClick={onClick}
32+
>
33+
<Image
34+
src="/icons/home/plus.svg"
35+
alt="할일 추가"
36+
width={16}
37+
height={16}
38+
priority
39+
/>
40+
할일 추가
41+
</Button>
42+
</div>
43+
);
44+
};
45+
46+
export default memo(TaskAddButton);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import useTaskFiltering from "@/hooks/useTaskFilter";
2+
import { useHomeData, useResetAlerts, useStartTask } from "@/hooks/useTasks";
3+
import type { Task } from "@/types/task";
4+
import { useMemo } from "react";
5+
import NoTaskScreen from "../NoTaskScreen";
6+
import TaskItem from "../TaskItem";
7+
import AllTaskButton from "./allTaskButton/AllTaskButton";
8+
9+
interface HasAllTasksOnlyScreenProps {
10+
handleTaskClick: (task: Task) => void;
11+
handleDeleteTask: (taskId: number) => void;
12+
}
13+
14+
const HasAllTasksOnlyScreen = ({
15+
handleTaskClick,
16+
handleDeleteTask,
17+
}: HasAllTasksOnlyScreenProps) => {
18+
const resetAlerts = useResetAlerts();
19+
20+
const { mutate: startTaskMutation } = useStartTask();
21+
22+
const { data: homeData } = useHomeData();
23+
24+
const { allTasks } = useTaskFiltering(homeData);
25+
26+
/**
27+
* * 마감이 임박한 순으로 정렬된 전체 할 일 (최대 2개)
28+
*/
29+
const topAllTasks = useMemo(() => {
30+
return [...allTasks]
31+
.sort(
32+
(a, b) =>
33+
new Date(a.dueDatetime).getTime() - new Date(b.dueDatetime).getTime(),
34+
)
35+
.slice(0, 2);
36+
}, [allTasks]);
37+
38+
return (
39+
<div className="mt-4">
40+
<NoTaskScreen
41+
firstText="이번주 마감할 일이 없어요."
42+
secondText="급한 할일부터 시작해볼까요?"
43+
thirdText="미루지 말고 여유있게 시작해보세요"
44+
/>
45+
46+
<div className="mb-4">
47+
{topAllTasks.map((task) => (
48+
<TaskItem
49+
key={task.id}
50+
taskId={task.id}
51+
onClick={() => handleTaskClick(task)}
52+
onDelete={() => handleDeleteTask(task.id)}
53+
onPreviewStart={(taskId) => taskId && startTaskMutation(taskId)}
54+
{...task}
55+
/>
56+
))}
57+
</div>
58+
59+
<AllTaskButton />
60+
</div>
61+
);
62+
};
63+
64+
export default HasAllTasksOnlyScreen;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Image from "next/image";
2+
import Link from "next/link";
3+
import { memo } from "react";
4+
5+
const AllTaskButton = () => {
6+
return (
7+
<Link href="/?tab=all">
8+
<div>
9+
<button
10+
type="button"
11+
className="flex w-full items-center justify-between px-4 py-4"
12+
>
13+
<span className="s2 text-text-neutral">전체 할일 더보기</span>
14+
<Image
15+
src="/icons/home/arrow-right.svg"
16+
alt="Arrow Right"
17+
width={24}
18+
height={24}
19+
priority
20+
/>
21+
</button>
22+
</div>
23+
</Link>
24+
);
25+
};
26+
27+
export default memo(AllTaskButton);

0 commit comments

Comments
 (0)