diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx index 9d6120a9..f0497c0b 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx @@ -22,8 +22,9 @@ import type { TodoPriorityTypes, TodoTimerStatusTypes, } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/todo-type"; +import type { KeyboardEvent, MouseEvent } from "react"; -import { convertDurationToTimeText } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time"; +import { convertDurationToTimeText } from "@/utils/todo/todo-time"; type PriorityLabelKeyTypes = "urgent" | "high" | "medium" | "low"; @@ -34,6 +35,10 @@ const PRIORITY_LABEL_KEY: Record = { LOW: "low", }; +const isInteractiveElement = (target: EventTarget | null) => + target instanceof HTMLElement && + Boolean(target.closest("button, input, label")); + export interface HomeTodoCardProps { todoId: number; title: string; @@ -46,6 +51,7 @@ export interface HomeTodoCardProps { timerStatus: TodoTimerStatusTypes; subtaskTitle?: string; isSubtaskCompleted?: boolean; + onClickTodo?: () => void; onToggleCompleted: (completed: boolean) => void; onTogglePlay: () => void; onToggleSubtaskCompleted?: (completed: boolean) => void; @@ -63,6 +69,7 @@ export const HomeTodoCard = ({ timerStatus, subtaskTitle, isSubtaskCompleted = false, + onClickTodo, onToggleCompleted, onTogglePlay, onToggleSubtaskCompleted, @@ -81,6 +88,21 @@ export const HomeTodoCard = ({ const priorityLabel = tCommon(`priority.${PRIORITY_LABEL_KEY[priority]}`); + const handleCardClick = (event: MouseEvent) => { + if (isInteractiveElement(event.target)) return; + + onClickTodo?.(); + }; + + const handleCardKeyDown = (event: KeyboardEvent) => { + if (isInteractiveElement(event.target)) return; + if (!onClickTodo) return; + if (event.key !== "Enter" && event.key !== " ") return; + + event.preventDefault(); + onClickTodo(); + }; + const titleRow = (
@@ -112,14 +134,19 @@ export const HomeTodoCard = ({ ); return ( -
{subtaskTitle ? ( @@ -167,6 +194,6 @@ export const HomeTodoCard = ({ {convertDurationToTimeText(durationSeconds)}
- +
); }; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx index 07602202..527452ca 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/HomeTodoContainer.tsx @@ -15,21 +15,10 @@ import { import { useHomeTodosByDate } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date"; import { useHomeViewMode } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-view-mode"; import { useHomeView } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_queries/use-home-view"; +import { DetailTodoModalContainer } from "@/components/todo-modal/detail/DetailTodoModalContainer"; import { DndSortableListProvider } from "@/providers/dnd/DndSortableListProvider"; import { formatDateKey } from "@/utils/date/date"; - -const TAG_LABEL_KEYS = [ - "dailyLife", - "work", - "exercise", - "assignment", - "additional", -] as const; - -type TagLabelKey = (typeof TAG_LABEL_KEYS)[number]; - -const isTagLabelKey = (value: string): value is TagLabelKey => - (TAG_LABEL_KEYS as readonly string[]).includes(value); +import { isTagLabelKey } from "@/utils/todo/tag-label"; export const HomeTodoContainer = () => { const tCommon = useTranslations("Common"); @@ -48,6 +37,7 @@ export const HomeTodoContainer = () => { handleToggleCompleted, handleTogglePlay, handleToggleSubtaskCompleted, + handleDeleteTodo, handleReorderTodo, } = useHomeTodosByDate(days); @@ -100,42 +90,57 @@ export const HomeTodoContainer = () => { const [firstSubtask] = todo.subtasks; return ( - - handleToggleCompleted(dateKey, todo.todoId, completed) - } + todo={todo} onTogglePlay={() => handleTogglePlay(dateKey, todo.todoId) } - onToggleSubtaskCompleted={ - firstSubtask - ? (completed) => - handleToggleSubtaskCompleted( - dateKey, - todo.todoId, - firstSubtask.subtaskId, - completed, - ) - : undefined - } - /> + onDelete={() => handleDeleteTodo(dateKey, todo.todoId)} + > + {(openDetailTodoModal) => ( + + handleToggleCompleted( + dateKey, + todo.todoId, + completed, + ) + } + onTogglePlay={() => + handleTogglePlay(dateKey, todo.todoId) + } + onToggleSubtaskCompleted={ + firstSubtask + ? (completed) => + handleToggleSubtaskCompleted( + dateKey, + todo.todoId, + firstSubtask.subtaskId, + completed, + ) + : undefined + } + /> + )} + ); })} 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 25e791d6..d7c51203 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 @@ -5,8 +5,8 @@ import { useTranslations } from "next-intl"; import type { ApiDayOfWeek } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_types/home-view-type"; 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 { CreateTodoModalContainer } from "@/components/todo-modal/create/CreateTodoModalContainer"; import { getToday, parseDateKey } from "@/utils/date/date"; export interface HomeDayHeaderContainerProps { diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts index 6abd08f8..cb3ef2c6 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts @@ -106,6 +106,14 @@ export const useHomeTodosByDate = (apiDays: HomeViewDay[]) => { ); }; + const handleDeleteTodo = (dateKey: string, todoId: number) => { + // TODO: API + setTodosByDate((prev) => ({ + ...prev, + [dateKey]: (prev[dateKey] ?? []).filter((todo) => todo.todoId !== todoId), + })); + }; + const handleReorderTodo = ( dateKey: string, fromIndex: number, @@ -136,6 +144,7 @@ export const useHomeTodosByDate = (apiDays: HomeViewDay[]) => { handleToggleCompleted, handleTogglePlay, handleToggleSubtaskCompleted, + handleDeleteTodo, handleReorderTodo, }; }; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayDateHeaderContainer.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayDateHeaderContainer.tsx index 7ed94e39..993ff376 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayDateHeaderContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/TodayDateHeaderContainer.tsx @@ -6,7 +6,8 @@ import type { TodoMock } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_ import { DateInformation } from "@/app/[locale]/(main)/(with-time-sidebar)/_components/DateInformation"; import { convertDateToDateText } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/date"; -import { CreateTodoModalContainer } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer"; +import { useCreateTodoSubmit } from "@/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/todo-modal/use-create-todo-submit"; +import { CreateTodoModalContainer } from "@/components/todo-modal/create/CreateTodoModalContainer"; import { getToday } from "@/utils/date/date"; import { getDayOfWeekKey } from "@/utils/date/get-day-of-week-key"; @@ -27,6 +28,7 @@ export const TodayDateHeaderContainer = ({ const date = convertDateToDateText(today); const dayKey = getDayOfWeekKey(today); const dayOfWeek = tCommon(`weekday.${dayKey}`); + const { handleSubmit } = useCreateTodoSubmit({ onCreate: onCreateTodo }); return (
@@ -38,7 +40,11 @@ export const TodayDateHeaderContainer = ({ totalCount={totalCount} completedCount={completedCount} /> - +
); }; 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 deleted file mode 100644 index 0885b885..00000000 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_containers/todo-modal/CreateTodoModalContainer.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"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/components/todo-modal/CreateTodoIconField.tsx b/apps/timo-web/components/todo-modal/common/TodoIconField.tsx similarity index 93% rename from apps/timo-web/components/todo-modal/CreateTodoIconField.tsx rename to apps/timo-web/components/todo-modal/common/TodoIconField.tsx index d7146266..057f87d9 100644 --- a/apps/timo-web/components/todo-modal/CreateTodoIconField.tsx +++ b/apps/timo-web/components/todo-modal/common/TodoIconField.tsx @@ -4,7 +4,7 @@ import { cn } from "@repo/timo-design-system/utils"; import type { TodoIconValue } from "@repo/timo-design-system/ui"; -export interface CreateTodoIconFieldProps { +export interface TodoIconFieldProps { icon: TodoIconValue | null; isIconPanelOpen: boolean; addIconLabel: string; @@ -14,7 +14,7 @@ export interface CreateTodoIconFieldProps { onRemoveIcon: () => void; } -export const CreateTodoIconField = ({ +export const TodoIconField = ({ icon, isIconPanelOpen, addIconLabel, @@ -22,7 +22,7 @@ export const CreateTodoIconField = ({ onTogglePanel, onSelectIcon, onRemoveIcon, -}: CreateTodoIconFieldProps) => { +}: TodoIconFieldProps) => { return (
{!isIconPanelOpen && icon ? ( diff --git a/apps/timo-web/components/todo-modal/CreateTodoMemoField.tsx b/apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx similarity index 100% rename from apps/timo-web/components/todo-modal/CreateTodoMemoField.tsx rename to apps/timo-web/components/todo-modal/create/CreateTodoMemoField.tsx diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContainer.tsx b/apps/timo-web/components/todo-modal/create/CreateTodoModalContainer.tsx similarity index 67% rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContainer.tsx rename to apps/timo-web/components/todo-modal/create/CreateTodoModalContainer.tsx index a0a0733f..8af78a01 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_containers/todo-modal/CreateTodoModalContainer.tsx +++ b/apps/timo-web/components/todo-modal/create/CreateTodoModalContainer.tsx @@ -4,21 +4,28 @@ import { AddTaskButton } from "@repo/timo-design-system/ui"; import { useTranslations } from "next-intl"; import { overlay } from "overlay-kit"; -import { useCreateTodoSubmit } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/todo-modal/use-create-todo-submit"; +import type { CreateTodoRequest } from "@/api/todo/todo-schema"; + import { AnimatedToast } from "@/components/toast/AnimatedToast"; -import { CreateTodoModalContent } from "@/components/todo-modal/CreateTodoModalContent"; +import { CreateTodoModalContent } from "@/components/todo-modal/create/CreateTodoModalContent"; +import { useCreateTodoSubmit } from "@/hooks/todo-modal/use-create-todo-submit"; export interface CreateTodoModalContainerProps { defaultDate?: Date; + buttonVariant?: "default" | "big"; + onSubmit?: (data: CreateTodoRequest) => void; } export const CreateTodoModalContainer = ({ defaultDate, + buttonVariant = "default", + onSubmit, }: CreateTodoModalContainerProps) => { const t = useTranslations("Home"); const tToast = useTranslations("Toast"); const { handleSubmit, isErrorToastOpen, closeErrorToast } = useCreateTodoSubmit(); + const submitTodo = onSubmit ?? handleSubmit; const handleAddClick = () => { overlay.open(({ isOpen, close, unmount }) => ( @@ -27,14 +34,18 @@ export const CreateTodoModalContainer = ({ onClose={close} onExited={unmount} defaultDate={defaultDate} - onSubmit={handleSubmit} + onSubmit={submitTodo} /> )); }; return ( <> - + ({ @@ -28,7 +28,7 @@ const createDefaultValues = (defaultDate?: Date): CreateTodoRequest => ({ title: "", subtasks: [], date: formatDateKey(defaultDate ?? new Date()), - duration: "0:00", + duration: "00:00", priority: null, tagId: null, repeatType: "NONE", @@ -113,7 +113,7 @@ export const CreateTodoModalContent = ({
- priorityField.onChange(level) } tagLabel={tagField.selectedTagLabel ?? t("createModal.tagLabel")} tags={tagField.tagLabels} selectedTag={tagField.selectedTagLabel} + addTagLabel={t("createModal.addTag")} onSelectTag={tagField.handleSelectTag} onAddTagClick={tagField.handleAddTagClick} hasMemo={Boolean(memoField.value?.trim())} diff --git a/apps/timo-web/components/todo-modal/CreateTodoTaskFields.tsx b/apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx similarity index 100% rename from apps/timo-web/components/todo-modal/CreateTodoTaskFields.tsx rename to apps/timo-web/components/todo-modal/create/CreateTodoTaskFields.tsx diff --git a/apps/timo-web/components/todo-modal/detail/DetailTodoMemoField.tsx b/apps/timo-web/components/todo-modal/detail/DetailTodoMemoField.tsx new file mode 100644 index 00000000..ca36ff68 --- /dev/null +++ b/apps/timo-web/components/todo-modal/detail/DetailTodoMemoField.tsx @@ -0,0 +1,36 @@ +import { useLayoutEffect, useRef } from "react"; + +export interface DetailTodoMemoFieldProps { + value: string; + placeholder: string; + onChange: (value: string) => void; + maxLength: number; +} + +export const DetailTodoMemoField = ({ + value, + placeholder, + onChange, + maxLength, +}: DetailTodoMemoFieldProps) => { + const textareaRef = useRef(null); + + useLayoutEffect(() => { + const textarea = textareaRef.current; + if (!textarea) return; + + textarea.style.height = "auto"; + textarea.style.height = `${textarea.scrollHeight}px`; + }, [value]); + + return ( +