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 = ({
)} -
- +
+
+ +
+ {isDimmed ? : }
); 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 = ({