diff --git a/apps/timo-web/api/todo/todo-schema.ts b/apps/timo-web/api/todo/todo-schema.ts new file mode 100644 index 00000000..dd2e590f --- /dev/null +++ b/apps/timo-web/api/todo/todo-schema.ts @@ -0,0 +1,91 @@ +import { z } from "zod"; + +import { apiResponseSchema } from "@/api/schema/response"; + +export const todoIconSchema = z.enum([ + "ICON_1", + "ICON_2", + "ICON_3", + "ICON_4", + "ICON_5", + "ICON_6", + "ICON_7", + "ICON_8", +]); + +export const todoPrioritySchema = z.enum([ + "VERY_HIGH", + "HIGH", + "MEDIUM", + "LOW", +]); + +export const todoRepeatTypeSchema = z.enum([ + "NONE", + "DAILY", + "WEEKLY", + "MONTHLY", +]); + +export const todoRepeatWeekdaySchema = z.enum([ + "MON", + "TUE", + "WED", + "THU", + "FRI", + "SAT", + "SUN", +]); + +const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/; +const DURATION_PATTERN = /^\d{1,3}:[0-5]\d$/; + +export const createTodoRequestSchema = z + .object({ + icon: todoIconSchema.nullable(), + title: z.string().trim().min(1), + subtasks: z.array(z.string().trim().min(1)).nullable(), + date: z.string().regex(DATE_PATTERN), + duration: z.string().regex(DURATION_PATTERN), + priority: todoPrioritySchema.nullable(), + tagId: z.number().int().positive().nullable(), + repeatType: todoRepeatTypeSchema, + repeatWeekdays: z.array(todoRepeatWeekdaySchema).nullable(), + repeatDayOfMonth: z.number().int().min(1).max(31).nullable(), + memo: z.string().nullable(), + }) + .superRefine((value, ctx) => { + if ( + value.repeatType === "WEEKLY" && + (!value.repeatWeekdays || value.repeatWeekdays.length === 0) + ) { + ctx.addIssue({ + code: "custom", + path: ["repeatWeekdays"], + message: "반복할 요일을 선택해주세요.", + }); + } + + if (value.repeatType === "MONTHLY" && !value.repeatDayOfMonth) { + ctx.addIssue({ + code: "custom", + path: ["repeatDayOfMonth"], + message: "반복할 날짜를 선택해주세요.", + }); + } + }); + +export type CreateTodoRequest = z.infer; + +export type TodoIcon = z.infer; +export type TodoPriority = z.infer; +export type TodoRepeatType = z.infer; +export type TodoRepeatWeekday = z.infer; + +export const createTodoResponseSchema = apiResponseSchema( + z.object({ + todoId: z.number(), + }), +); + +export type CreateTodoResponse = z.infer; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeDateInformation.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeDateInformation.tsx similarity index 100% rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeDateInformation.tsx rename to apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeDateInformation.tsx diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx similarity index 93% rename from apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx rename to apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx index 77d4344a..054b37a3 100644 --- a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/HomeTodoCard.tsx +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-card/HomeTodoCard.tsx @@ -25,11 +25,10 @@ import type { import { convertDurationToTimeText } from "@/app/[locale]/(main)/(with-time-sidebar)/home/_utils/todo-time"; -const PRIORITY_MAP: Record< - TodoPriorityTypes, - "urgent" | "high" | "medium" | "low" -> = { - URGENT: "urgent", +type PriorityLabelKeyTypes = "urgent" | "high" | "medium" | "low"; + +const PRIORITY_LABEL_KEY: Record = { + VERY_HIGH: "urgent", HIGH: "high", MEDIUM: "medium", LOW: "low", @@ -76,12 +75,11 @@ export const HomeTodoCard = ({ const sortableStyle = { transform: CSS.Transform.toString(transform), transition, - touchAction: "none", }; const isRunning = timerStatus === "RUNNING"; - const priorityLabel = tCommon(`priority.${PRIORITY_MAP[priority]}`); + const priorityLabel = tCommon(`priority.${PRIORITY_LABEL_KEY[priority]}`); const titleRow = (
@@ -148,7 +146,7 @@ export const HomeTodoCard = ({
{tagName && } diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoIconField.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoIconField.tsx new file mode 100644 index 00000000..d7146266 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoIconField.tsx @@ -0,0 +1,67 @@ +import { ChevronUpIcon } from "@repo/timo-design-system/icons"; +import { IconGraphic, IconSelector } from "@repo/timo-design-system/ui"; +import { cn } from "@repo/timo-design-system/utils"; + +import type { TodoIconValue } from "@repo/timo-design-system/ui"; + +export interface CreateTodoIconFieldProps { + icon: TodoIconValue | null; + isIconPanelOpen: boolean; + addIconLabel: string; + onOpenPanel: () => void; + onTogglePanel: () => void; + onSelectIcon: (icon: TodoIconValue) => void; + onRemoveIcon: () => void; +} + +export const CreateTodoIconField = ({ + icon, + isIconPanelOpen, + addIconLabel, + onOpenPanel, + onTogglePanel, + onSelectIcon, + onRemoveIcon, +}: CreateTodoIconFieldProps) => { + return ( +
+ {!isIconPanelOpen && icon ? ( + + ) : ( + + )} + + {isIconPanelOpen && ( + + )} +
+ ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsx b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsx new file mode 100644 index 00000000..b12a565a --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_components/todo-modal/CreateTodoMemoField.tsx @@ -0,0 +1,21 @@ +export interface CreateTodoMemoFieldProps { + value: string; + placeholder: string; + onChange: (value: string) => void; +} + +export const CreateTodoMemoField = ({ + value, + placeholder, + onChange, +}: CreateTodoMemoFieldProps) => { + return ( +