diff --git a/src/app/(protected)/dashboard/_components/goal/GoalSkeleton.tsx b/src/app/(protected)/dashboard/_components/goal/GoalSkeleton.tsx index bdf4bfc..b65c012 100644 --- a/src/app/(protected)/dashboard/_components/goal/GoalSkeleton.tsx +++ b/src/app/(protected)/dashboard/_components/goal/GoalSkeleton.tsx @@ -9,13 +9,13 @@ export default function GoalSkeleton() {
-
-
+
+
-
-
+
+
@@ -33,7 +33,6 @@ export default function GoalSkeleton() { title="DONE" variant="done"> diff --git a/src/app/(protected)/dashboard/_components/todos/progress/ProgressTodos.tsx b/src/app/(protected)/dashboard/_components/todos/progress/ProgressTodos.tsx index 7a251f9..a8f4517 100644 --- a/src/app/(protected)/dashboard/_components/todos/progress/ProgressTodos.tsx +++ b/src/app/(protected)/dashboard/_components/todos/progress/ProgressTodos.tsx @@ -3,6 +3,8 @@ import ProgressCardSkeleton from "@/components/skeleton/ProgressCardSkeleton"; import ProgressContent from "./ProgressContent"; import { useAuthStore } from "@/store/useAuthStore"; import { AsyncBoundary } from "@/app/(protected)/_components/AsyncBoundary"; +import { FallbackProps } from "react-error-boundary"; +import Button from "@/components/common/button/Button"; export default function ProgressTodos() { const user = useAuthStore((state) => state.user); @@ -22,7 +24,20 @@ export default function ProgressTodos() {
- }> + } + errorFallback={({ error, resetErrorBoundary }: FallbackProps) => ( +
+

+ {error.message} +

+ +
+ )}>
diff --git a/src/app/(protected)/dashboard/_components/todos/recent/RecentTodos.tsx b/src/app/(protected)/dashboard/_components/todos/recent/RecentTodos.tsx index 5d63441..6cf2948 100644 --- a/src/app/(protected)/dashboard/_components/todos/recent/RecentTodos.tsx +++ b/src/app/(protected)/dashboard/_components/todos/recent/RecentTodos.tsx @@ -4,6 +4,8 @@ import { ChevronRightIcon } from "@heroicons/react/24/outline"; import { ListSkeleton } from "@/components/skeleton/ListSkeleton"; import RecentTodosContent from "./RecentTodosContent"; import { AsyncBoundary } from "@/app/(protected)/_components/AsyncBoundary"; +import { FallbackProps } from "react-error-boundary"; +import Button from "@/components/common/button/Button"; export default function RecentTodos() { return ( @@ -30,9 +32,22 @@ export default function RecentTodos() { loadingFallback={ - }> + } + errorFallback={({ error, resetErrorBoundary }: FallbackProps) => ( +
+

+ {error.message} +

+ +
+ )}>
diff --git a/src/app/(protected)/goals/[goalId]/_components/Goal.tsx b/src/app/(protected)/goals/[goalId]/_components/Goal.tsx index 562dc7e..df60db2 100644 --- a/src/app/(protected)/goals/[goalId]/_components/Goal.tsx +++ b/src/app/(protected)/goals/[goalId]/_components/Goal.tsx @@ -9,6 +9,7 @@ export default function Goal({ goalTodos }: { goalTodos: ListTodoType[] }) { const goalTodoChecked = goalTodos.filter((goalTodo) => !goalTodo.checked); const goalTodoCheckedDone = goalTodos.filter((goalTodo) => goalTodo.checked); + return (
0 ? goalTodos[0].id : null; + useEffect(() => { + const handleResize = () => { + if (window.innerWidth >= 640) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + }; + + handleResize(); + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + document.body.style.overflow = ""; + }; + }, []); return ( <>
diff --git a/src/app/(protected)/goals/[goalId]/_components/GoalContainerSkeleton.tsx b/src/app/(protected)/goals/[goalId]/_components/GoalContainerSkeleton.tsx index 62a24a0..cabcfdc 100644 --- a/src/app/(protected)/goals/[goalId]/_components/GoalContainerSkeleton.tsx +++ b/src/app/(protected)/goals/[goalId]/_components/GoalContainerSkeleton.tsx @@ -3,31 +3,27 @@ import { ListSkeleton } from "@/components/skeleton/ListSkeleton"; export default function GoalContainerSkeleton() { return (
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
+
+
+
@@ -36,6 +32,7 @@ export default function GoalContainerSkeleton() {
diff --git a/src/app/(protected)/goals/[goalId]/_components/GoalHeader.tsx b/src/app/(protected)/goals/[goalId]/_components/GoalHeader.tsx index a7766c1..08427b2 100644 --- a/src/app/(protected)/goals/[goalId]/_components/GoalHeader.tsx +++ b/src/app/(protected)/goals/[goalId]/_components/GoalHeader.tsx @@ -11,6 +11,7 @@ import { useDeleteGoalMutation } from "@/hooks/queries/goals/useDeleteGoalMutati import ConfirmModal from "@/components/common/popup-modal/ConfirmModal"; import { useRouter } from "next/navigation"; import { toast } from "@/lib/toast"; +import BaseInput from "@/components/common/input/base-input/BaseInput"; type GoalHeaderProps = { goalId: string; @@ -41,7 +42,7 @@ export default function GoalHeader({ goalId }: GoalHeaderProps) { text: "수정하기", onClick: () => { closeDropdown(); - setEditTitle(goal?.title ?? ""); + setEditTitle(""); setIsEditing(true); }, }, @@ -66,6 +67,11 @@ export default function GoalHeader({ goalId }: GoalHeaderProps) { ); }; + const handleCancelEdit = () => { + setEditTitle(""); + setIsEditing(false); + }; + const handleConfim = () => { deleteGoal(numericGoalId, { onSuccess: () => { @@ -86,11 +92,13 @@ export default function GoalHeader({ goalId }: GoalHeaderProps) {

{goal?.title}

) : (
- setEditTitle(e.target.value)} + placeholder="수정할 목표를 적어주세요." onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); @@ -98,10 +106,16 @@ export default function GoalHeader({ goalId }: GoalHeaderProps) { } }} /> + - 수정 완료 + 수정 + + + 취소
)} diff --git a/src/app/(protected)/goals/[goalId]/_components/GoalSection.tsx b/src/app/(protected)/goals/[goalId]/_components/GoalSection.tsx index bc8e05e..219cc7a 100644 --- a/src/app/(protected)/goals/[goalId]/_components/GoalSection.tsx +++ b/src/app/(protected)/goals/[goalId]/_components/GoalSection.tsx @@ -42,7 +42,7 @@ export default function GoalSection({ className="grid sm:gap-0.5 lg:gap-1" items={items} onToggleChecked={onToggleChecked} - containerClassName="sm:h-110" + containerClassName="sm:h-110 overflow-hidden overflow-y-auto" /> )}
diff --git a/src/components/common/input/base-input/BaseInput.tsx b/src/components/common/input/base-input/BaseInput.tsx index 19885ad..6c4aab4 100644 --- a/src/components/common/input/base-input/BaseInput.tsx +++ b/src/components/common/input/base-input/BaseInput.tsx @@ -9,6 +9,7 @@ export interface BaseInputProps { id?: string; value: string; onChange: (e: React.ChangeEvent) => void; + onKeyDown?: (e: React.KeyboardEvent) => void; placeholder?: string; type?: InputType; className?: string; @@ -20,6 +21,7 @@ export default function BaseInput({ id, value, onChange, + onKeyDown, placeholder = "", type = "text", className, @@ -46,6 +48,7 @@ export default function BaseInput({ value={value} placeholder={placeholder} onChange={onChange} + onKeyDown={onKeyDown} className={inputClassName} /> {rightIcon} diff --git a/tests/unit/goals/goals.test.tsx b/tests/unit/goals/goals.test.tsx index 0518c61..784c3db 100644 --- a/tests/unit/goals/goals.test.tsx +++ b/tests/unit/goals/goals.test.tsx @@ -34,6 +34,26 @@ describe("목표 영역", () => { expect(screen.getByText("수정하기")).toBeInTheDocument(); expect(screen.getByText("삭제하기")).toBeInTheDocument(); }); + it("목표 수정 중 취소를 누르면 수정 모드가 종료된다", async () => { + const user = userEvent.setup(); + + renderWithQueryClient(); + + await user.click(screen.getByLabelText("goal-options")); + await user.click(screen.getByText("수정하기")); + + const input = screen.getByRole("textbox"); + expect(input).toBeInTheDocument(); + + await user.type(input, "할일"); + + await user.click(screen.getByText("취소")); + + expect(screen.queryByRole("textbox")).not.toBeInTheDocument(); + + expect(updateGoal).not.toHaveBeenCalled(); + }); + it("목표를 수정하면 입력한 값으로 변경된다", async () => { const user = userEvent.setup(); (updateGoal as jest.Mock).mockResolvedValueOnce({ @@ -53,7 +73,7 @@ describe("목표 영역", () => { await user.clear(input); await user.type(input, "새 목표"); - await user.click(screen.getByText("수정 완료")); + await user.click(screen.getByText("수정")); expect(await screen.findByText("새 목표")).toBeInTheDocument();