From 78e3bdbda0909a76d73e6c9fab5fda382694969e Mon Sep 17 00:00:00 2001 From: geonhee-min Date: Thu, 11 Dec 2025 17:03:25 +0900 Subject: [PATCH] =?UTF-8?q?issue=20#60=20-=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=201=EC=B0=A8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20-=20=EC=9D=BC=EC=A0=95=20=EB=8B=B9?= =?UTF-8?q?=EC=9D=BC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=201?= =?UTF-8?q?=EC=B0=A8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/calendar.tsx | 2 +- src/components/ui/card.tsx | 2 +- src/components/ui/popover.tsx | 2 +- src/const/schedule/ScheduleType.ts | 2 +- src/data/form/schedule/listSchedule.schema.ts | 6 +- .../request/schedule/CreateScheduleRequest.ts | 8 +- .../request/schedule/ScheduleListRequest.ts | 5 +- src/index.css | 4 +- src/ui/component/Header.tsx | 2 +- src/ui/component/calendar/CustomCalendar.tsx | 323 ++++++++++++++---- src/ui/component/calendar/CustomCalendarCN.ts | 55 +++ .../schedule/SchedulePopover.tsx | 16 +- .../schedule/content/ContentProps.ts | 0 .../content/ScheduleCreateContent.tsx | 15 +- .../content/ScheduleDetailContent.tsx | 0 .../schedule/content/ScheduleListContent.tsx | 22 +- .../content/ScheduleUpdateContent.tsx | 0 .../popover}/ColorPickPopover.tsx | 2 +- .../popover}/DatePickPopover.tsx | 0 .../popover}/FilterPopover.tsx | 0 .../popover}/ParticipantPopover.tsx | 0 .../popover}/TimePickPopover.tsx | 0 .../popover}/TypePickPopover.tsx | 0 .../schedule/tile/ScheduleListTile.tsx | 34 ++ src/util/Converter.ts | 16 + 25 files changed, 405 insertions(+), 111 deletions(-) create mode 100644 src/ui/component/calendar/CustomCalendarCN.ts rename src/ui/component/{popover => }/schedule/SchedulePopover.tsx (85%) rename src/ui/component/{popover => }/schedule/content/ContentProps.ts (100%) rename src/ui/component/{popover => }/schedule/content/ScheduleCreateContent.tsx (95%) rename src/ui/component/{popover => }/schedule/content/ScheduleDetailContent.tsx (100%) rename src/ui/component/{popover => }/schedule/content/ScheduleListContent.tsx (83%) rename src/ui/component/{popover => }/schedule/content/ScheduleUpdateContent.tsx (100%) rename src/ui/component/{popover/schedule => schedule/popover}/ColorPickPopover.tsx (98%) rename src/ui/component/{popover/schedule => schedule/popover}/DatePickPopover.tsx (100%) rename src/ui/component/{popover/schedule => schedule/popover}/FilterPopover.tsx (100%) rename src/ui/component/{popover/schedule => schedule/popover}/ParticipantPopover.tsx (100%) rename src/ui/component/{popover/schedule => schedule/popover}/TimePickPopover.tsx (100%) rename src/ui/component/{popover/schedule => schedule/popover}/TypePickPopover.tsx (100%) create mode 100644 src/ui/component/schedule/tile/ScheduleListTile.tsx create mode 100644 src/util/Converter.ts diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx index d3fa42c..414133b 100644 --- a/src/components/ui/calendar.tsx +++ b/src/components/ui/calendar.tsx @@ -214,7 +214,7 @@ function CalendarDayButton({ ref={ref} variant="ghost" size="icon" - data-day={day.date.toLocaleDateString()} + data-day={day.date.toISOString()} data-selected-single={ modifiers.selected && !modifiers.range_start && diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index 681ad98..0f2f49b 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
= { - 'once': '한 번만', + 'once': '반복없음', 'daily': '매일', 'weekly': '매주', 'monthly': '매월', diff --git a/src/data/form/schedule/listSchedule.schema.ts b/src/data/form/schedule/listSchedule.schema.ts index b371d89..b2f712f 100644 --- a/src/data/form/schedule/listSchedule.schema.ts +++ b/src/data/form/schedule/listSchedule.schema.ts @@ -4,12 +4,8 @@ export const ListScheduleSchema = z.object({ name: z .string() .optional() - , startDate: z + , date: z .date() - .optional() - , endDate: z - .date() - .optional() , status: z .string() .optional() diff --git a/src/data/request/schedule/CreateScheduleRequest.ts b/src/data/request/schedule/CreateScheduleRequest.ts index 0946ce9..295302d 100644 --- a/src/data/request/schedule/CreateScheduleRequest.ts +++ b/src/data/request/schedule/CreateScheduleRequest.ts @@ -4,8 +4,8 @@ import type { ScheduleType } from "@/const/schedule/ScheduleType"; export class CreateScheduleRequest { name: string; content: string; - startDate: Date; - endDate: Date; + startDate: string; + endDate: string; status: ScheduleStatus; type: ScheduleType; startTime: string; @@ -16,8 +16,8 @@ export class CreateScheduleRequest { constructor ( name: string, content: string, - startDate: Date, - endDate: Date, + startDate: string, + endDate: string, status: ScheduleStatus, type: ScheduleType, startTime: string, diff --git a/src/data/request/schedule/ScheduleListRequest.ts b/src/data/request/schedule/ScheduleListRequest.ts index 4d94a6b..7339a2d 100644 --- a/src/data/request/schedule/ScheduleListRequest.ts +++ b/src/data/request/schedule/ScheduleListRequest.ts @@ -2,8 +2,9 @@ import type { ScheduleStatus } from "@/const/schedule/ScheduleStatus"; import type { ScheduleType } from "@/const/schedule/ScheduleType"; export class ScheduleListRequest { - startDate?: Date; - endDate?: Date; + date?: string; + startDate?: string; + endDate?: string; typeList?: ScheduleType[]; styleList?: string[]; status?: ScheduleStatus; diff --git a/src/index.css b/src/index.css index 1ea1679..f8cbd26 100644 --- a/src/index.css +++ b/src/index.css @@ -144,9 +144,9 @@ input[type="number"] { } .custom-rdp-week:not(:first-child) { - @apply border-t!; + @apply border-t! border-indigo-200!; } .custom-rdp-day:not(:first-child) { - @apply border-l!; + @apply border-l! border-indigo-200!; } \ No newline at end of file diff --git a/src/ui/component/Header.tsx b/src/ui/component/Header.tsx index 129b1ec..9eea7e7 100644 --- a/src/ui/component/Header.tsx +++ b/src/ui/component/Header.tsx @@ -17,7 +17,7 @@ export default function Header() { } return ( -
+
diff --git a/src/ui/component/calendar/CustomCalendar.tsx b/src/ui/component/calendar/CustomCalendar.tsx index f572bed..6fe0d83 100644 --- a/src/ui/component/calendar/CustomCalendar.tsx +++ b/src/ui/component/calendar/CustomCalendar.tsx @@ -1,37 +1,261 @@ import { cn } from "@/lib/utils"; import { Calendar } from "@/components/ui/calendar"; -import { useLayoutEffect, useRef, useState } from "react"; -import { getDefaultClassNames } from "react-day-picker"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { isSameDay, getWeeksInMonth, getWeekOfMonth } from "date-fns"; -import { SchedulePopover } from "../popover/schedule/SchedulePopover"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import { Popover, PopoverTrigger } from "@/components/ui/popover"; +import { isSameDay, getWeeksInMonth, getWeekOfMonth, eachDayOfInterval, parse, startOfMonth, startOfWeek, endOfWeek, endOfMonth, format, isSameMonth } from "date-fns"; +import { SchedulePopover } from "../schedule/SchedulePopover"; +import { ScheduleNetwork } from "@/network/ScheduleNetwork"; +import { ScheduleListData } from "@/data/response"; +import { CustomCalendarCN } from "./CustomCalendarCN"; +import { toast } from "sonner"; +import { Converter } from "@/util/Converter"; interface CustomCalendarProps { data?: any; } +interface EventBarPosition extends ScheduleListData { + positionStyle: React.CSSProperties; + trackIndex: number; +} + +const MAX_VISIBLE_EVENTS = 3; +const OVERFLOW_TRACK_INDEX = MAX_VISIBLE_EVENTS + 1; +const TRACK_HEIGHT = 20; +const TOP_OFFSET_FROM_CELL = 35; +const DATE_FORMAT_ARIA = 'EEEE, MMMM do, yyyy'; +const DATE_FORMAT_KEY = 'yyyyMMdd'; + export const CustomCalendar = ({ data }: CustomCalendarProps) => { const [weekCount, setWeekCount] = useState(5); const [selectedDate, setSelectedDate] = useState(undefined); const [popoverOpen, setPopoverOpen] = useState(false); const [popoverSide, setPopoverSide] = useState<'right' | 'left'>('right'); const [popoverAlign, setPopoverAlign] = useState<'start' | 'end'>('end'); - const defaultClassNames = getDefaultClassNames(); + const [month, setMonth] = useState(new Date()); + const [currentDataMonth, setCurrentDataMonth] = useState(month); + const [scheduleList, setScheduleList] = useState>([]); + const [barPositions, setBarPositions] = useState>([]); + const scheduleNetwork = new ScheduleNetwork(); const containerRef = useRef(null); + const updateWeekCount = () => { if (containerRef === null) return; if (!containerRef.current) return; - const weeks = containerRef.current.querySelectorAll('.rdp-week'); if (weeks?.length) setWeekCount(weeks.length); } - useLayoutEffect(() => { + useEffect(() => { updateWeekCount(); }, []); - + + useLayoutEffect(() => { + updateWeekCount(); + + const reqList = async () => { + const requestedMonth = month; + + const monthStart = startOfMonth(month); + const monthEnd = endOfMonth(month); + + const startDate = startOfWeek(monthStart, { weekStartsOn: 0 }); + const endDate = endOfWeek(monthEnd, { weekStartsOn: 0 }); + + const data = { + startDate: Converter.dateToUTC9(startDate), + endDate: Converter.dateToUTC9(endDate) + }; + + const result = await scheduleNetwork.getList(data); + + if (result.data.success) { + if (result.data.data) { + if (isSameMonth(requestedMonth, month)) { + setScheduleList(result.data.data!); + setCurrentDataMonth(month); + } + setScheduleList(result.data.data); + } + } + } + + requestAnimationFrame(() => { + const reqListPromise = reqList; + + toast.promise( + reqListPromise, + { + loading: `${month.getFullYear()}년 ${month.getMonth() + 1}월 일정을 불러오는 중입니다`, + success: `일정을 불러왔습니다.`, + error: `일정을 불러오는 데에 실패하였습니다.\n잠시 후 다시 시도해주십시오.`, + duration: 1000 + }, + ) + updateWeekCount(); + }); + }, [month]); + + useLayoutEffect(() => { + if (!isSameMonth(month, currentDataMonth)) { + setBarPositions([]); + return; + } + + if (!containerRef.current || scheduleList.length === 0) { + setBarPositions([]); + return; + } + + const containerRect = containerRef.current.getBoundingClientRect(); + const dayCells = containerRef.current.querySelectorAll('.rdp-day button'); + + const cellInfoMap = new Map(); + + dayCells.forEach((cell) => { + const dayButton = cell as HTMLButtonElement; + const ariaLabel = dayButton.getAttribute('aria-label'); + + if (ariaLabel) { + try { + const parsedDate = parse(ariaLabel, DATE_FORMAT_ARIA, new Date()); + + if (!isNaN(parsedDate.getTime())) { + const dateKey = format(parsedDate, DATE_FORMAT_KEY); + + cellInfoMap.set(dateKey, { + cell: dayButton, + rect: dayButton.getBoundingClientRect() + }); + + } + } catch (e) {} + } + }); + + const scheduleListWithTrack: (ScheduleListData & { trackIndex: number })[] = []; + const occupiedTrackList = new Map(); + const overflowCountMap = new Map(); + + const sortedScheduleList = [...scheduleList].sort((a, b) => + new Date(b.endDate).getTime() - new Date(b.startDate).getTime() - + (new Date(a.endDate).getTime() - new Date(a.startDate).getTime()) + ); + + sortedScheduleList.forEach(schedule => { + const startDateObj = new Date(schedule.startDate); + const endDateObj = new Date(schedule.endDate); + + const eventDays = eachDayOfInterval({ start: startDateObj, end: endDateObj }); + + let assignedTrack = -1; + + for (let track = 0; track < MAX_VISIBLE_EVENTS; track++) { + let isAvailable = true; + for (const day of eventDays) { + const dayKey = format(day, DATE_FORMAT_KEY); + if (cellInfoMap.has(dayKey)) { + const occupied = occupiedTrackList.get(dayKey) || []; + + if (occupied.includes(track)) { + isAvailable = false; + break; + } + } + } + + if (isAvailable) { + assignedTrack = track; + break; + } + } + + if (assignedTrack !== -1) { + for (const day of eventDays) { + const dayKey = format(day, DATE_FORMAT_KEY); + if (cellInfoMap.has(dayKey)) { + const occupied = occupiedTrackList.get(dayKey) || []; + occupiedTrackList.set(dayKey, [...occupied, assignedTrack]); + } + } + scheduleListWithTrack.push({ ...schedule, trackIndex: assignedTrack }); + } else { + for (const day of eventDays) { + const dayKey = format(day, DATE_FORMAT_KEY); + if (cellInfoMap.has(dayKey)) { + overflowCountMap.set(dayKey, (overflowCountMap.get(dayKey) || 0) + 1); + } + } + } + }); + + const regularPositions: EventBarPosition[] = []; + + scheduleListWithTrack.forEach(schedule => { + const startKey = format(new Date(schedule.startDate), DATE_FORMAT_KEY); + const endKey = format(new Date(schedule.endDate), DATE_FORMAT_KEY); + + const startInfo = cellInfoMap.get(startKey); + const endInfo = cellInfoMap.get(endKey); + + if (startInfo && endInfo) { + const startRect = startInfo.rect; + const endRect = endInfo.rect; + + const baseTop = startRect.top - containerRect.top; + const top = baseTop + TOP_OFFSET_FROM_CELL + (schedule.trackIndex * (TRACK_HEIGHT + 5)); + const left = startRect.left - containerRect.left + 5; + const width = endRect.right - startRect.left - 10; + + regularPositions.push({ + ...schedule, + trackIndex: schedule.trackIndex, + positionStyle: { + top: `${top}px`, + left: `${left}px`, + width: `${width}px`, + height: `${TRACK_HEIGHT}px` + } + }); + } + }); + + const overflowPositions: EventBarPosition[] = []; + overflowCountMap.forEach((count, dayKey) => { + const dayInfo = cellInfoMap.get(dayKey); + + if (dayInfo) { + const dayRect = dayInfo.rect; + + const baseTop = dayRect.top - containerRect.top; + const top = baseTop + TOP_OFFSET_FROM_CELL + ((OVERFLOW_TRACK_INDEX - 1) * (TRACK_HEIGHT + 5)); + const left = dayRect.left - containerRect.left + 5; + const width = dayInfo.cell.clientWidth - 10; + + overflowPositions.push({ + id: `overflow-${dayKey}`, + name: `${count} more`, + startDate: Converter.dateToUTC9(new Date()), + endDate: Converter.dateToUTC9(new Date()), + style: '#9CA3AF', + trackIndex: OVERFLOW_TRACK_INDEX, + positionStyle: { + top: `${top}px`, + left: `${left}px`, + width: `${width}px`, + height: `${TRACK_HEIGHT}px` + } + } as EventBarPosition) + } + }); + setBarPositions([...regularPositions, ...overflowPositions]); + }, [scheduleList, month, currentDataMonth]); + + const handleMonthChange = async (month: Date) => { + setMonth(month); + } + const handleOpenChange = (open: boolean) => { setPopoverOpen(open); if (!open) { @@ -82,7 +306,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { return (
{ > { - // month 바뀐 직후 DOM 변화가 생기므로 다음 프레임에서 계산 - requestAnimationFrame(() => { - updateWeekCount(); - }); - }} - classNames={{ - months: cn( - defaultClassNames.months, - "w-full h-full relative" - ), - nav: cn( - defaultClassNames.nav, - "flex w-full item-center gap-1 justify-around absolute top-0 inset-x-0" - ), - month: cn( - defaultClassNames.month, - "h-full w-full flex flex-col" - ), - month_grid: cn( - defaultClassNames.month_grid, - "w-full h-full flex-1" - ), - weeks: cn( - defaultClassNames.weeks, - "w-full h-full" - ), - weekdays: cn( - defaultClassNames.weekdays, - "w-full" - ), - week: cn( - defaultClassNames.week, - `w-full`, - 'custom-rdp-week' - ), - day: cn( - defaultClassNames.day, - `w-[calc(100%/7)] rounded-none`, - 'custom-rdp-day' - ), - day_button: cn( - defaultClassNames.day_button, - "h-full w-full flex p-2 justify-start items-start", - "hover:bg-transparent", - "data-[selected-single=true]:bg-transparent data-[selected-single=true]:text-black" - ), - selected: cn( - defaultClassNames.selected, - "h-full border-0 fill-transparent" - ), - today: cn( - defaultClassNames.today, - "h-full" - ), - - }} + month={month} + onMonthChange={handleMonthChange} + classNames={CustomCalendarCN} styles={{ day: { height: `calc(100%/${weekCount})` @@ -174,13 +344,26 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => { DayButton: ({ day, ...props}) => ( ) }} /> + { + barPositions.map(pos => ( +
+ {pos.name} +
+ )) + } { const [mode, setMode] = useState<'list' | 'create' | 'detail' | 'update'>('list'); + useEffect(() => { + if (!open) { + setTimeout(() => { + setMode('list'); + }, 150); + } + }, [open]); + const DetailContent = () => { return (
diff --git a/src/ui/component/popover/schedule/content/ContentProps.ts b/src/ui/component/schedule/content/ContentProps.ts similarity index 100% rename from src/ui/component/popover/schedule/content/ContentProps.ts rename to src/ui/component/schedule/content/ContentProps.ts diff --git a/src/ui/component/popover/schedule/content/ScheduleCreateContent.tsx b/src/ui/component/schedule/content/ScheduleCreateContent.tsx similarity index 95% rename from src/ui/component/popover/schedule/content/ScheduleCreateContent.tsx rename to src/ui/component/schedule/content/ScheduleCreateContent.tsx index 795c4ba..19082f8 100644 --- a/src/ui/component/popover/schedule/content/ScheduleCreateContent.tsx +++ b/src/ui/component/schedule/content/ScheduleCreateContent.tsx @@ -21,11 +21,12 @@ import { useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { toast } from 'sonner'; import * as z from 'zod'; -import { ColorPickPopover } from '../ColorPickPopover'; -import { DatePickPopover } from '../DatePickPopover'; -import { TimePickPopover } from '../TimePickPopover'; -import { TypePickPopover } from '../TypePickPopover'; +import { ColorPickPopover } from '../popover/ColorPickPopover'; +import { DatePickPopover } from '../popover/DatePickPopover'; +import { TimePickPopover } from '../popover/TimePickPopover'; +import { TypePickPopover } from '../popover/TypePickPopover'; import type { ScheduleCreateContentProps } from './ContentProps'; +import { Converter } from '@/util/Converter'; export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign }: ScheduleCreateContentProps) => { const [colorPopoverOpen, setColorPopoverOpen] = useState(false); @@ -75,8 +76,8 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign if (isLoading) return; const data = { name, - startDate, - endDate, + startDate: Converter.dateToUTC9(startDate), + endDate: Converter.dateToUTC9(endDate), content, startTime: standardTimeToContinentalTime(startTime), endTime: standardTimeToContinentalTime(endTime), @@ -226,7 +227,7 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign
{ const [isLoading, setIsLoading] = useState(false); @@ -21,8 +23,7 @@ export const ScheduleListContent = ({ date, setMode, popoverAlign, popoverSide, resolver: zodResolver(ListScheduleSchema), defaultValues: { name: undefined, - startDate: undefined, - endDate: undefined, + date: date, status: undefined, typeList: undefined, styleList: undefined @@ -31,8 +32,7 @@ export const ScheduleListContent = ({ date, setMode, popoverAlign, popoverSide, const { name, - startDate, - endDate, + date: searchDate, status, typeList, styleList @@ -52,8 +52,7 @@ export const ScheduleListContent = ({ date, setMode, popoverAlign, popoverSide, const reqList = async () => { const data = { name, - startDate: date, - endDate: date, + date: Converter.dateToUTC9(searchDate), status: status as ScheduleStatus | undefined, styleList, typeList: typeList as ScheduleType[] | undefined @@ -80,9 +79,16 @@ export const ScheduleListContent = ({ date, setMode, popoverAlign, popoverSide,
- +
+ { scheduleList.map(schedule => ( + + )) } +
diff --git a/src/ui/component/popover/schedule/content/ScheduleUpdateContent.tsx b/src/ui/component/schedule/content/ScheduleUpdateContent.tsx similarity index 100% rename from src/ui/component/popover/schedule/content/ScheduleUpdateContent.tsx rename to src/ui/component/schedule/content/ScheduleUpdateContent.tsx diff --git a/src/ui/component/popover/schedule/ColorPickPopover.tsx b/src/ui/component/schedule/popover/ColorPickPopover.tsx similarity index 98% rename from src/ui/component/popover/schedule/ColorPickPopover.tsx rename to src/ui/component/schedule/popover/ColorPickPopover.tsx index 6c07032..487cbfe 100644 --- a/src/ui/component/popover/schedule/ColorPickPopover.tsx +++ b/src/ui/component/schedule/popover/ColorPickPopover.tsx @@ -81,7 +81,7 @@ export const ColorPickPopover = ({ setColor }: ColorPickPopoverProps) => { className={cn( "w-0 h-0 border-l-8 border-l-transparent border-r-8 border-r-transparent border-b-14 border-b-indigo-300", "group-hover:border-b-white trnasition-all duration-150", - seeMore && "rotate-180" + !seeMore && "rotate-180" )} />
diff --git a/src/ui/component/popover/schedule/DatePickPopover.tsx b/src/ui/component/schedule/popover/DatePickPopover.tsx similarity index 100% rename from src/ui/component/popover/schedule/DatePickPopover.tsx rename to src/ui/component/schedule/popover/DatePickPopover.tsx diff --git a/src/ui/component/popover/schedule/FilterPopover.tsx b/src/ui/component/schedule/popover/FilterPopover.tsx similarity index 100% rename from src/ui/component/popover/schedule/FilterPopover.tsx rename to src/ui/component/schedule/popover/FilterPopover.tsx diff --git a/src/ui/component/popover/schedule/ParticipantPopover.tsx b/src/ui/component/schedule/popover/ParticipantPopover.tsx similarity index 100% rename from src/ui/component/popover/schedule/ParticipantPopover.tsx rename to src/ui/component/schedule/popover/ParticipantPopover.tsx diff --git a/src/ui/component/popover/schedule/TimePickPopover.tsx b/src/ui/component/schedule/popover/TimePickPopover.tsx similarity index 100% rename from src/ui/component/popover/schedule/TimePickPopover.tsx rename to src/ui/component/schedule/popover/TimePickPopover.tsx diff --git a/src/ui/component/popover/schedule/TypePickPopover.tsx b/src/ui/component/schedule/popover/TypePickPopover.tsx similarity index 100% rename from src/ui/component/popover/schedule/TypePickPopover.tsx rename to src/ui/component/schedule/popover/TypePickPopover.tsx diff --git a/src/ui/component/schedule/tile/ScheduleListTile.tsx b/src/ui/component/schedule/tile/ScheduleListTile.tsx new file mode 100644 index 0000000..03c92f4 --- /dev/null +++ b/src/ui/component/schedule/tile/ScheduleListTile.tsx @@ -0,0 +1,34 @@ +import { ScheduleTypeLabel } from "@/const/schedule/ScheduleType"; +import { ScheduleListData } from "@/data/response"; +import { Converter } from "@/util/Converter"; + +interface ScheduleListTileProps { + setMode: (mode: 'list' | 'create' | 'detail' | 'update') => void; + data: ScheduleListData; +} +export const ScheduleListTile = ({ setMode, data }: ScheduleListTileProps) => { + const formatter = Converter.isoStringToFormattedString; + + return ( +
+
+
+
+ + {data.name} + +
+
+ {formatter(data.startDate)} - {formatter(data.endDate)} +
+
+ +
+ ) +} \ No newline at end of file diff --git a/src/util/Converter.ts b/src/util/Converter.ts new file mode 100644 index 0000000..349f1bd --- /dev/null +++ b/src/util/Converter.ts @@ -0,0 +1,16 @@ +import { format } from "date-fns"; + +export class Converter { + static dateToUTC9(date: Date) { + const utc9Date = new Date(date); + utc9Date.setHours(9); + + return utc9Date.toISOString(); + } + + static isoStringToFormattedString(isoString: string) { + const isoDate = new Date(isoString); + const dateFormatter = "yyyy년 MM월 dd일"; + return format(isoDate, dateFormatter); + } +} \ No newline at end of file