diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeDateInformation.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_components/DateInformation.tsx
similarity index 92%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeDateInformation.tsx
rename to apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_components/DateInformation.tsx
index 1f05ed61..217b9f83 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeDateInformation.tsx
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/_components/DateInformation.tsx
@@ -1,7 +1,7 @@
import { TodayBadge } from "@repo/timo-design-system/ui";
import { cn } from "@repo/timo-design-system/utils";
-interface HomeDateInformationProps {
+interface DateInformationProps {
date: string;
dayOfWeek: string;
isHoliday: boolean;
@@ -10,14 +10,14 @@ interface HomeDateInformationProps {
completedCount: number;
}
-export const HomeDateInformation = ({
+export const DateInformation = ({
date,
dayOfWeek,
isHoliday,
isToday,
totalCount,
completedCount,
-}: HomeDateInformationProps) => {
+}: DateInformationProps) => {
return (
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-card/HomeDayHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-card/HomeDayHeaderContainer.tsx
index 7f2c897c..53371868 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-card/HomeDayHeaderContainer.tsx
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-card/HomeDayHeaderContainer.tsx
@@ -4,7 +4,7 @@ import { useTranslations } from "next-intl";
import type { ApiDayOfWeek } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type";
-import { HomeDateInformation } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeDateInformation";
+import { DateInformation } from "@/app/[locale]/(main)/(with-time-sidebar)/_components/DateInformation";
import { CreateTodoModalContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContainer";
import { convertDateToDateText } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date";
import { getToday, parseDateKey } from "@/utils/date";
@@ -31,7 +31,7 @@ export const HomeDayHeaderContainer = ({
return (
-
void;
+}
+
+export const TodayDateHeaderContainer = ({
+ completedCount,
+ totalCount,
+ onCreateTodo,
+}: TodayDateHeaderContainerProps) => {
+ const tCommon = useTranslations("Common");
+
+ const today = getToday();
+ const date = convertDateToDateText(today);
+ const dayKey = getDayOfWeekKey(today);
+ const dayOfWeek = tCommon(`weekday.${dayKey}`);
+
+ return (
+
+
+
+
+ );
+};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx
index 9618cb1c..dad74d65 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import type { ReactNode } from "react";
@@ -17,10 +17,9 @@ export interface TodayTodoCardContainerProps {
toolbar: TodayTodoCardToolbar;
timerStatus: "RUNNING" | "PAUSED" | "STOPPED";
icon?: ReactNode;
- onIconClick?: () => void;
+ onCardClick?: () => void;
onCheck?: () => void;
onPlay?: () => void;
- onDelete?: () => void;
onSubTodoCheck?: (id: number) => void;
}
@@ -28,24 +27,19 @@ export const TodayTodoCardContainer = ({
title,
isDone: initialIsDone,
icon,
- onIconClick,
subTodos: initialSubTodos,
toolbar,
timerStatus,
+ onCardClick,
onCheck,
onPlay,
- onDelete,
onSubTodoCheck,
}: TodayTodoCardContainerProps) => {
const [isHovered, setIsHovered] = useState(false);
- const [isPlaying, setIsPlaying] = useState(timerStatus === "RUNNING");
const [isDone, setIsDone] = useState(initialIsDone);
const [subTodos, setSubTodos] = useState(initialSubTodos);
- useEffect(() => {
- setIsPlaying(timerStatus === "RUNNING");
- }, [timerStatus]);
-
+ const isPlaying = timerStatus === "RUNNING";
const isDimmed = isDone && !isHovered;
const handleCheck = () => {
@@ -54,7 +48,6 @@ export const TodayTodoCardContainer = ({
if (next) {
setSubTodos((prev) => prev.map((s) => ({ ...s, isDone: true })));
if (isPlaying) {
- setIsPlaying(false);
onPlay?.();
}
}
@@ -63,7 +56,6 @@ export const TodayTodoCardContainer = ({
const handlePlay = () => {
if (!isDone) {
- setIsPlaying((prev) => !prev);
onPlay?.();
}
};
@@ -82,12 +74,11 @@ export const TodayTodoCardContainer = ({
isDimmed={isDimmed}
isPlaying={isPlaying}
icon={icon}
- onIconClick={onIconClick}
subTodos={subTodos}
toolbar={toolbar}
+ onCardClick={onCardClick}
onCheck={handleCheck}
onPlay={handlePlay}
- onDelete={onDelete ?? (() => {})}
onSubTodoCheck={handleSubTodoCheck}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx
index eaf45a28..81e4668e 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoListContainer.tsx
@@ -1,11 +1,8 @@
"use client";
-import { useState } from "react";
-
-import type { TodoMock } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock";
-
+import { TodayDateHeaderContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayDateHeaderContainer";
import { TodayTodoCardContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayTodoCardContainer";
-import { todayTodoMocks } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock";
+import { useTodayTodoList } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList";
import { convertDurationToTimeText } from "@/utils/convert-duration-to-time-text";
import { formatDate } from "@/utils/date";
@@ -17,84 +14,51 @@ const PRIORITY_MAP = {
} as const;
export const TodayTodoListContainer = () => {
- const [todos, setTodos] = useState(todayTodoMocks);
- const runningTodoId =
- todos.find((t) => t.timerStatus === "RUNNING")?.todoId ?? null;
-
- const handlePlay = (todoId: number) => {
- // TODO: API
- setTodos((prev) =>
- prev.map((t) => ({
- ...t,
- timerStatus:
- t.todoId === todoId && t.timerStatus !== "RUNNING"
- ? "RUNNING"
- : "STOPPED",
- })),
- );
- };
-
- const handleCheck = (todoId: number) => {
- // TODO: API
- setTodos((prev) =>
- prev.map((t) =>
- t.todoId === todoId
- ? { ...t, completed: !t.completed, timerStatus: "STOPPED" }
- : t,
- ),
- );
- };
-
- const handleDelete = (todoId: number) => {
- // TODO: API
- setTodos((prev) => prev.filter((t) => t.todoId !== todoId));
- };
+ const {
+ todos,
+ runningTodoId,
+ handlePlay,
+ handleCheck,
+ handleAddTodo,
+ handleSubTodoCheck,
+ } = useTodayTodoList();
- const handleSubTodoCheck = (todoId: number, subtaskId: number) => {
- // TODO: API
- setTodos((prev) =>
- prev.map((t) =>
- t.todoId === todoId
- ? {
- ...t,
- subtasks: t.subtasks.map((s) =>
- s.subtaskId === subtaskId
- ? { ...s, completed: !s.completed }
- : s,
- ),
- }
- : t,
- ),
- );
- };
+ const completedCount = todos.filter((todo) => todo.completed).length;
return (
-
- {todos.map((todo) => (
-
({
- id: s.subtaskId,
- text: s.content,
- isDone: s.completed,
- }))}
- onPlay={() => handlePlay(todo.todoId)}
- onCheck={() => handleCheck(todo.todoId)}
- onDelete={() => handleDelete(todo.todoId)}
- onSubTodoCheck={(id) => handleSubTodoCheck(todo.todoId, id)}
- />
- ))}
+
+
+
+ {todos.map((todo) => (
+ ({
+ id: s.subtaskId,
+ text: s.content,
+ isDone: s.completed,
+ }))}
+ onPlay={() => handlePlay(todo.todoId)}
+ onCheck={() => handleCheck(todo.todoId)}
+ onSubTodoCheck={(id) => handleSubTodoCheck(todo.todoId, id)}
+ />
+ ))}
+
);
};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer.tsx
new file mode 100644
index 00000000..0885b885
--- /dev/null
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { AddTaskButton } from "@repo/timo-design-system/ui";
+import { useTranslations } from "next-intl";
+import { overlay } from "overlay-kit";
+
+import type { TodoMock } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock";
+
+import { useCreateTodoSubmit } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit";
+import { CreateTodoModalContent } from "@/components/todo-modal/CreateTodoModalContent";
+
+export interface CreateTodoModalContainerProps {
+ defaultDate?: Date;
+ onCreate: (todo: TodoMock) => void;
+}
+
+export const CreateTodoModalContainer = ({
+ defaultDate,
+ onCreate,
+}: CreateTodoModalContainerProps) => {
+ const t = useTranslations("Home");
+ const { handleSubmit } = useCreateTodoSubmit({ onCreate });
+
+ const handleAddClick = () => {
+ overlay.open(({ isOpen, close, unmount }) => (
+
+ ));
+ };
+
+ return (
+
+ );
+};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts
new file mode 100644
index 00000000..d58b4698
--- /dev/null
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit.ts
@@ -0,0 +1,46 @@
+import type { CreateTodoRequest, TodoPriority } from "@/api/todo/todo-schema";
+import type { TodoMock } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock";
+
+import { convertApiDurationToSeconds } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_utils/todo-time";
+
+const PRIORITY_TO_MOCK: Record = {
+ VERY_HIGH: "URGENT",
+ HIGH: "HIGH",
+ MEDIUM: "MEDIUM",
+ LOW: "LOW",
+};
+
+// 목데이터: 실제 API 호출 없이 로컬 상태에만 즉시 반영한다.
+const buildTodoFromRequest = (data: CreateTodoRequest): TodoMock => ({
+ todoId: Date.now(),
+ icon: data.icon ?? "",
+ title: data.title,
+ completed: false,
+ date: data.date,
+ durationSeconds: convertApiDurationToSeconds(data.duration),
+ priority: PRIORITY_TO_MOCK[data.priority ?? "MEDIUM"],
+ tag: { tagId: data.tagId ?? 0, name: "" },
+ hasMemo: Boolean(data.memo?.trim()),
+ isRepeated: data.repeatType !== "NONE",
+ timerStatus: "STOPPED",
+ sortOrder: 0,
+ subtasks: (data.subtasks ?? []).map((content, index) => ({
+ subtaskId: Date.now() + index,
+ content,
+ completed: false,
+ })),
+});
+
+export interface UseCreateTodoSubmitParams {
+ onCreate: (todo: TodoMock) => void;
+}
+
+export const useCreateTodoSubmit = ({
+ onCreate,
+}: UseCreateTodoSubmitParams) => {
+ const handleSubmit = (data: CreateTodoRequest) => {
+ onCreate(buildTodoFromRequest(data));
+ };
+
+ return { handleSubmit };
+};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts
new file mode 100644
index 00000000..d4a24664
--- /dev/null
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts
@@ -0,0 +1,75 @@
+"use client";
+
+import { useState } from "react";
+
+import type { TodoMock } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock";
+
+import { todayTodoMocks } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock";
+
+export const useTodayTodoList = () => {
+ const [todos, setTodos] = useState(todayTodoMocks);
+
+ const runningTodoId =
+ todos.find((todo) => todo.timerStatus === "RUNNING")?.todoId ?? null;
+
+ const handlePlay = (todoId: number) => {
+ // TODO: API
+ setTodos((prev) =>
+ prev.map((todo) => ({
+ ...todo,
+ timerStatus:
+ todo.todoId === todoId && todo.timerStatus !== "RUNNING"
+ ? "RUNNING"
+ : "STOPPED",
+ })),
+ );
+ };
+
+ const handleCheck = (todoId: number) => {
+ // TODO: API
+ setTodos((prev) =>
+ prev.map((todo) =>
+ todo.todoId === todoId
+ ? { ...todo, completed: !todo.completed, timerStatus: "STOPPED" }
+ : todo,
+ ),
+ );
+ };
+
+ const handleDelete = (todoId: number) => {
+ // TODO: API
+ setTodos((prev) => prev.filter((todo) => todo.todoId !== todoId));
+ };
+
+ const handleAddTodo = (todo: TodoMock) => {
+ setTodos((prev) => [...prev, todo]);
+ };
+
+ const handleSubTodoCheck = (todoId: number, subtaskId: number) => {
+ // TODO: API
+ setTodos((prev) =>
+ prev.map((todo) =>
+ todo.todoId === todoId
+ ? {
+ ...todo,
+ subtasks: todo.subtasks.map((s) =>
+ s.subtaskId === subtaskId
+ ? { ...s, completed: !s.completed }
+ : s,
+ ),
+ }
+ : todo,
+ ),
+ );
+ };
+
+ return {
+ todos,
+ runningTodoId,
+ handlePlay,
+ handleCheck,
+ handleDelete,
+ handleAddTodo,
+ handleSubTodoCheck,
+ };
+};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock.ts
index 2b96261b..41ebd086 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock.ts
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_mocks/today-todo-mock.ts
@@ -74,4 +74,89 @@ export const todayTodoMocks: TodoMock[] = [
sortOrder: 2,
subtasks: [],
},
+ {
+ todoId: 4,
+ icon: "ICON_4",
+ title: "API 연동 명세서 작성",
+ completed: false,
+ date: "2026-07-09",
+ durationSeconds: 7200,
+ priority: "HIGH",
+ tag: { tagId: 3, name: "개발" },
+ hasMemo: false,
+ isRepeated: false,
+ timerStatus: "STOPPED",
+ sortOrder: 3,
+ subtasks: [
+ { subtaskId: 3, content: "엔드포인트 목록 정리", completed: false },
+ { subtaskId: 4, content: "요청/응답 스펙 작성", completed: false },
+ ],
+ },
+ {
+ todoId: 5,
+ icon: "ICON_5",
+ title: "주간 회의 준비",
+ completed: false,
+ date: "2026-07-09",
+ durationSeconds: 2700,
+ priority: "MEDIUM",
+ tag: { tagId: 4, name: "회의" },
+ hasMemo: true,
+ isRepeated: true,
+ timerStatus: "STOPPED",
+ sortOrder: 4,
+ subtasks: [{ subtaskId: 5, content: "안건 정리", completed: true }],
+ },
+ {
+ todoId: 6,
+ icon: "ICON_6",
+ title: "코드 리뷰 완료하기",
+ completed: false,
+ date: "2026-07-09",
+ durationSeconds: 5400,
+ priority: "URGENT",
+ tag: { tagId: 1, name: "작업" },
+ hasMemo: false,
+ isRepeated: false,
+ timerStatus: "STOPPED",
+ sortOrder: 5,
+ subtasks: [],
+ },
+ {
+ todoId: 7,
+ icon: "ICON_7",
+ title: "스토리북 스토리 작성",
+ completed: true,
+ date: "2026-07-09",
+ durationSeconds: 4500,
+ priority: "LOW",
+ tag: { tagId: 3, name: "개발" },
+ hasMemo: false,
+ isRepeated: false,
+ timerStatus: "STOPPED",
+ sortOrder: 6,
+ subtasks: [
+ { subtaskId: 6, content: "Button 스토리", completed: true },
+ { subtaskId: 7, content: "Modal 스토리", completed: true },
+ ],
+ },
+ {
+ todoId: 8,
+ icon: "ICON_8",
+ title: "퍼포먼스 이슈 분석",
+ completed: false,
+ date: "2026-07-09",
+ durationSeconds: 9000,
+ priority: "HIGH",
+ tag: { tagId: 3, name: "개발" },
+ hasMemo: true,
+ isRepeated: false,
+ timerStatus: "STOPPED",
+ sortOrder: 7,
+ subtasks: [
+ { subtaskId: 8, content: "Lighthouse 측정", completed: false },
+ { subtaskId: 9, content: "번들 사이즈 확인", completed: false },
+ { subtaskId: 10, content: "리렌더링 최적화", completed: false },
+ ],
+ },
];
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_utils/todo-time.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_utils/todo-time.ts
new file mode 100644
index 00000000..35838c4a
--- /dev/null
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_utils/todo-time.ts
@@ -0,0 +1,13 @@
+import { SECONDS_PER_MINUTE } from "@/constants/time";
+
+/**
+ * API duration 필드(mm:ss)를 초 단위로 변환한다.
+ * @param duration - "mm:ss" 형식의 시간 문자열
+ * @returns 변환된 초
+ */
+export const convertApiDurationToSeconds = (duration: string): number => {
+ const [minutesText, secondsText] = duration.split(":");
+ const minutes = Number(minutesText) || 0;
+ const seconds = Number(secondsText) || 0;
+ return minutes * SECONDS_PER_MINUTE + seconds;
+};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx
index 6e632bd5..88fda426 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx
+++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/page.tsx
@@ -3,11 +3,11 @@ import { TodayTodoListContainer } from "@/app/[locale]/(main)/(with-time-sidebar
export default function TodayPage() {
return (
-
+
);
}
diff --git a/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx
index ed1d2331..79ea0ca0 100644
--- a/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx
+++ b/apps/timo-web/app/[locale]/(main)/today/_components/TodayTodoCard.tsx
@@ -2,19 +2,18 @@ import {
PlayDisabledIcon,
PlayIcon,
StopIcon,
+ TrashDisableIcon,
+ TrashOnIcon,
} from "@repo/timo-design-system/icons";
import {
Checkbox,
PlayButton,
- PriorityIcon,
+ TodoToolbar,
+ type PriorityLevel,
} from "@repo/timo-design-system/ui";
import { cn } from "@repo/timo-design-system/utils";
-import type { ComponentProps, ReactNode } from "react";
-
-import { CreateTodoToolbar } from "@/components/CreateTodoToolbar";
-
-type PriorityTypes = ComponentProps["priority"];
+import type { KeyboardEvent, ReactNode } from "react";
const CARD_STYLE = {
active: {
@@ -37,8 +36,9 @@ export interface SubTodo {
export interface TodayTodoCardToolbar {
date: string;
+ dateValue: Date;
time: string;
- priority: PriorityTypes;
+ priority?: PriorityLevel;
tag?: string;
hasMemo: boolean;
hasRepeat: boolean;
@@ -50,87 +50,105 @@ export interface TodayTodoCardProps {
isDimmed: boolean;
isPlaying: boolean;
icon?: ReactNode;
- onIconClick?: () => void;
subTodos: SubTodo[];
toolbar: TodayTodoCardToolbar;
+ onCardClick?: () => void;
onCheck: () => void;
onPlay: () => void;
- onDelete: () => void;
onSubTodoCheck: (id: number) => void;
onMouseEnter: () => void;
onMouseLeave: () => void;
}
+const stopPropagation = (e: { stopPropagation: () => void }) =>
+ e.stopPropagation();
+
export const TodayTodoCard = ({
title,
isDone,
isDimmed,
isPlaying,
icon,
- onIconClick,
subTodos,
toolbar,
+ onCardClick,
onCheck,
onPlay,
- onDelete,
onSubTodoCheck,
onMouseEnter,
onMouseLeave,
}: TodayTodoCardProps) => {
const style = CARD_STYLE[isDimmed ? "done" : "active"];
+ const handleCardKeyDown = (e: KeyboardEvent) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ onCardClick?.();
+ }
+ };
+
return (
-
onCheck()} />
- {icon && (
-
- )}
+
+ onCheck()} />
+
+ {icon && {icon}}
{title}
-
- {isDone ? (
-
- ) : isPlaying ? (
-
- ) : (
-
- )}
-
+
+
+ {isDone ? (
+
+ ) : isPlaying ? (
+
+ ) : (
+
+ )}
+
+
{subTodos.length > 0 && (
{subTodos.map((sub) => (
-
- onSubTodoCheck(sub.id)}
- />
+
+ onSubTodoCheck(sub.id)}
+ />
+
{sub.text}
@@ -139,18 +157,34 @@ export const TodayTodoCard = ({
)}
-
);
diff --git a/apps/timo-web/app/[locale]/oauth/callback/page.tsx b/apps/timo-web/app/[locale]/oauth/callback/page.tsx
index e60c35af..26ede93a 100644
--- a/apps/timo-web/app/[locale]/oauth/callback/page.tsx
+++ b/apps/timo-web/app/[locale]/oauth/callback/page.tsx
@@ -1,7 +1,6 @@
import { OauthCallbackContainer } from "@/app/[locale]/oauth/callback/_containers/OauthCallbackContainer";
import { AsyncBoundary } from "@/components/boundary/AsyncBoundary";
-
export default function OauthCallbackPage() {
return (
diff --git a/apps/timo-web/components/CreateTodoToolbar.tsx b/apps/timo-web/components/CreateTodoToolbar.tsx
deleted file mode 100644
index a6c0523c..00000000
--- a/apps/timo-web/components/CreateTodoToolbar.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-"use client";
-
-import {
- CalendarBlueIcon,
- CalendarDisableIcon,
- CalendarOnIcon,
- ClockBlueIcon,
- ClockDisableIcon,
- ClockOnIcon,
- MemoBlueIcon,
- MemoDisableIcon,
- MemoOnIcon,
- RepeatBlueIcon,
- RepeatTodoDisableIcon,
- RepeatTodoOnIcon,
- TrashBlueIcon,
- TrashDisableIcon,
- TrashOnIcon,
-} from "@repo/timo-design-system/icons";
-import { PriorityIcon, TagIcon } from "@repo/timo-design-system/ui";
-import { cn } from "@repo/timo-design-system/utils";
-
-import type { ComponentProps } from "react";
-
-type Priority = ComponentProps["priority"];
-
-type ActiveItem =
- | "date"
- | "time"
- | "priority"
- | "tag"
- | "memo"
- | "repeat"
- | "delete";
-
-export interface CreateTodoToolbarProps {
- date: string;
- time: string;
- priority: Priority;
- tag?: string;
- hasMemo: boolean;
- hasRepeat?: boolean;
- hasDelete?: boolean;
- isDimmed?: boolean;
- activeItem?: ActiveItem;
- onDateClick?: () => void;
- onTimeClick?: () => void;
- onPriorityClick?: () => void;
- onTagClick?: () => void;
- onMemoClick?: () => void;
- onRepeatClick?: () => void;
- onDeleteClick?: () => void;
-}
-
-export const CreateTodoToolbar = ({
- date,
- time,
- priority,
- tag,
- hasMemo,
- hasRepeat,
- hasDelete,
- isDimmed,
- activeItem,
- onDateClick,
- onTimeClick,
- onPriorityClick,
- onTagClick,
- onMemoClick,
- onRepeatClick,
- onDeleteClick,
-}: CreateTodoToolbarProps) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/tag-modal/CreateTagModalContainer.tsx b/apps/timo-web/components/tag-modal/CreateTagModalContainer.tsx
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/tag-modal/CreateTagModalContainer.tsx
rename to apps/timo-web/components/tag-modal/CreateTagModalContainer.tsx
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoIconField.tsx b/apps/timo-web/components/todo-modal/CreateTodoIconField.tsx
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoIconField.tsx
rename to apps/timo-web/components/todo-modal/CreateTodoIconField.tsx
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsx b/apps/timo-web/components/todo-modal/CreateTodoMemoField.tsx
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsx
rename to apps/timo-web/components/todo-modal/CreateTodoMemoField.tsx
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx b/apps/timo-web/components/todo-modal/CreateTodoModalContent.tsx
similarity index 87%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx
rename to apps/timo-web/components/todo-modal/CreateTodoModalContent.tsx
index a128e213..c6493f29 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContent.tsx
+++ b/apps/timo-web/components/todo-modal/CreateTodoModalContent.tsx
@@ -10,25 +10,25 @@ import type { CreateTodoRequest } from "@/api/todo/todo-schema";
import type { PriorityLevel } from "@repo/timo-design-system/ui";
import { createTodoRequestSchema } from "@/api/todo/todo-schema";
-import { CreateTodoIconField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoIconField";
-import { CreateTodoMemoField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField";
-import { CreateTodoTaskFields } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields";
-import { useIconField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field";
-import { useRepeatField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field";
import { useSubtaskField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-subtask-field";
-import { useTagField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field";
-import { useTimeField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field";
import { useTitleField } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-title-field";
-import { formatDateToIsoDate } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date";
import { OverlayModal } from "@/components/modal/OverlayModal";
import { AnimatedToast } from "@/components/toast/AnimatedToast";
+import { CreateTodoIconField } from "@/components/todo-modal/CreateTodoIconField";
+import { CreateTodoMemoField } from "@/components/todo-modal/CreateTodoMemoField";
+import { CreateTodoTaskFields } from "@/components/todo-modal/CreateTodoTaskFields";
+import { useIconField } from "@/hooks/todo-modal/use-icon-field";
+import { useRepeatField } from "@/hooks/todo-modal/use-repeat-field";
+import { useTagField } from "@/hooks/todo-modal/use-tag-field";
+import { useTimeField } from "@/hooks/todo-modal/use-time-field";
+import { formatDateKey } from "@/utils/date";
const createDefaultValues = (defaultDate?: Date): CreateTodoRequest => ({
icon: null,
title: "",
subtasks: [],
- date: formatDateToIsoDate(defaultDate ?? new Date()),
- duration: "00:00",
+ date: formatDateKey(defaultDate ?? new Date()),
+ duration: "0:00",
priority: null,
tagId: null,
repeatType: "NONE",
@@ -150,9 +150,7 @@ export const CreateTodoModalContent = ({
: t("createModal.dateLabel")
}
date={dateValue}
- onDateChange={(next) =>
- dateField.onChange(formatDateToIsoDate(next))
- }
+ onDateChange={(next) => dateField.onChange(formatDateKey(next))}
timeLabel={timeField.timeDisplay}
timeOptions={timeField.timeOptions}
time={timeField.duration}
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields.tsx b/apps/timo-web/components/todo-modal/CreateTodoTaskFields.tsx
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoTaskFields.tsx
rename to apps/timo-web/components/todo-modal/CreateTodoTaskFields.tsx
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field.ts b/apps/timo-web/hooks/todo-modal/use-icon-field.ts
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-icon-field.ts
rename to apps/timo-web/hooks/todo-modal/use-icon-field.ts
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.ts b/apps/timo-web/hooks/todo-modal/use-repeat-field.ts
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-repeat-field.ts
rename to apps/timo-web/hooks/todo-modal/use-repeat-field.ts
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx b/apps/timo-web/hooks/todo-modal/use-tag-field.tsx
similarity index 95%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx
rename to apps/timo-web/hooks/todo-modal/use-tag-field.tsx
index 9e1ec51e..f6fffae3 100644
--- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-tag-field.tsx
+++ b/apps/timo-web/hooks/todo-modal/use-tag-field.tsx
@@ -6,7 +6,7 @@ import { useController } from "react-hook-form";
import type { CreateTodoRequest } from "@/api/todo/todo-schema";
import type { Control } from "react-hook-form";
-import { CreateTagModalContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_containers/tag-modal/CreateTagModalContainer";
+import { CreateTagModalContainer } from "@/components/tag-modal/CreateTagModalContainer";
const TAG_NAMES = [
"dailyLife",
diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.ts b/apps/timo-web/hooks/todo-modal/use-time-field.ts
similarity index 100%
rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-time-field.ts
rename to apps/timo-web/hooks/todo-modal/use-time-field.ts
diff --git a/apps/timo-web/messages/en.json b/apps/timo-web/messages/en.json
index 485a9573..fbcbfbba 100644
--- a/apps/timo-web/messages/en.json
+++ b/apps/timo-web/messages/en.json
@@ -29,7 +29,8 @@
"medium": "Average",
"low": "Low"
},
- "close": "Close"
+ "close": "Close",
+ "delete": "Delete"
},
"Home": {
"today": "Today",
diff --git a/apps/timo-web/messages/ko.json b/apps/timo-web/messages/ko.json
index 5b51025b..3e00f944 100644
--- a/apps/timo-web/messages/ko.json
+++ b/apps/timo-web/messages/ko.json
@@ -29,7 +29,8 @@
"medium": "보통",
"low": "낮음"
},
- "close": "닫기"
+ "close": "닫기",
+ "delete": "삭제"
},
"Home": {
"today": "오늘",
diff --git a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx
index 6262eba1..7559659c 100644
--- a/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx
+++ b/packages/timo-design-system/src/components/layout/dropdown/Dropdown.tsx
@@ -18,11 +18,14 @@ import {
unregisterOpenFloatingLayer,
} from "../../../lib";
+export type DropdownAlign = "left" | "right";
+
interface DropdownContextValue {
isOpen: boolean;
toggle: () => void;
close: () => void;
triggerRef: RefObject;
+ align: DropdownAlign;
}
const DropdownContext = createContext(null);
@@ -42,10 +45,16 @@ const useDropdownContext = (): DropdownContextValue => {
export interface DropdownProps {
children: ReactNode;
className?: string;
+ align?: DropdownAlign;
onOpenChange?: (isOpen: boolean) => void;
}
-const DropdownRoot = ({ children, className, onOpenChange }: DropdownProps) => {
+const DropdownRoot = ({
+ children,
+ className,
+ align = "left",
+ onOpenChange,
+}: DropdownProps) => {
const [isOpen, setIsOpen] = useState(false);
const rootRef = useRef(null);
const triggerRef = useRef(null);
@@ -86,7 +95,9 @@ const DropdownRoot = ({ children, className, onOpenChange }: DropdownProps) => {
const close = () => setIsOpen(false);
return (
-
+
{children}
@@ -125,7 +136,7 @@ const DropdownPanel = ({
className,
...rest
}: DropdownPanelProps) => {
- const { isOpen } = useDropdownContext();
+ const { isOpen, align } = useDropdownContext();
if (!isOpen) return null;
@@ -133,7 +144,8 @@ const DropdownPanel = ({