From 22277528ba1192727a141ced07a251b9e0a025a5 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 11:12:51 +0900 Subject: [PATCH 01/72] =?UTF-8?q?Feat:=20reservation=20api=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20API=20ENDPOINTS=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/{app => }/api/reservation.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename apps/web/{app => }/api/reservation.ts (84%) diff --git a/apps/web/app/api/reservation.ts b/apps/web/api/reservation.ts similarity index 84% rename from apps/web/app/api/reservation.ts rename to apps/web/api/reservation.ts index bb40fe46..7b93dac4 100644 --- a/apps/web/app/api/reservation.ts +++ b/apps/web/api/reservation.ts @@ -1,4 +1,4 @@ -// apps/web/app/api/reservations.ts +import { API_ENDPOINTS } from "@repo/constants"; import { type IReservation, type TReservationStatus } from "@repo/types/src/reservationType"; import { axiosRequester } from "@/lib/axios"; @@ -12,7 +12,7 @@ export const getUserReservations = async (params: GetUserReservationsParams): Pr const { data } = await axiosRequester({ options: { method: "GET", - url: `/dashboard/${userId}`, + url: API_ENDPOINTS.RESERVATION.GET_USER_RESERVATIONS(userId), }, }); @@ -22,7 +22,7 @@ export const getUserReservations = async (params: GetUserReservationsParams): Pr // 아이템 타입 및 날짜에 대한 예약 조회 interface GetReservationsByTypeAndDateParams { itemType: "room" | "seat" | "equipment"; - date?: string; // YYYY-MM-DD 형식 + date: string; status?: TReservationStatus; } @@ -33,7 +33,7 @@ export const getReservationsByTypeAndDate = async ( const { data } = await axiosRequester({ options: { method: "GET", - url: `/${itemType}`, + url: API_ENDPOINTS.RESERVATION.GET_RESERVATIONS_BY_TYPE_AND_DATE(itemType, date), params: { date, status, @@ -56,7 +56,7 @@ export const createReservation = async (params: CreateReservationParams): Promis const { data: reservation } = await axiosRequester<{ message: string; savedReservation: IReservation }>({ options: { method: "POST", - url: `/${itemId}`, + url: API_ENDPOINTS.RESERVATION.CREATE_RESERVATION(itemId), data, }, }); From 6035d2d01031be5b15dd2bdb94359cd5a41c0433 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 15:41:49 +0900 Subject: [PATCH 02/72] =?UTF-8?q?Chore:=20reservation=20api=20rename(?= =?UTF-8?q?=EB=B3=B5=EC=88=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/api/{reservation.ts => reservations.ts} | 1 - 1 file changed, 1 deletion(-) rename apps/web/api/{reservation.ts => reservations.ts} (99%) diff --git a/apps/web/api/reservation.ts b/apps/web/api/reservations.ts similarity index 99% rename from apps/web/api/reservation.ts rename to apps/web/api/reservations.ts index 7b93dac4..7e4125fc 100644 --- a/apps/web/api/reservation.ts +++ b/apps/web/api/reservations.ts @@ -35,7 +35,6 @@ export const getReservationsByTypeAndDate = async ( method: "GET", url: API_ENDPOINTS.RESERVATION.GET_RESERVATIONS_BY_TYPE_AND_DATE(itemType, date), params: { - date, status, }, }, From b59b319981adc4dbdc24d08c5f0486c364cd502a Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 15:42:36 +0900 Subject: [PATCH 03/72] =?UTF-8?q?Feat:=20items=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/api/items.ts | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 apps/web/api/items.ts diff --git a/apps/web/api/items.ts b/apps/web/api/items.ts new file mode 100644 index 00000000..cf9c7a96 --- /dev/null +++ b/apps/web/api/items.ts @@ -0,0 +1,73 @@ +import { API_ENDPOINTS } from "@repo/constants"; +import { type TBaseItem, type TItemType, type IRoom, type ISeat, type IEquipment } from "@repo/types"; +import { axiosRequester } from "@/lib/axios"; + +// 특정 타입의 아이템 조회 +interface GetAllItemsParams { + itemType: TItemType; +} + +export const getAllItems = async (params: GetAllItemsParams): Promise => { + const { itemType } = params; + const { data } = await axiosRequester({ + options: { + method: "GET", + url: API_ENDPOINTS.ITEMS.GET_ALL(itemType), + }, + }); + + return data; +}; + +// 아이템 생성 +interface CreateItemParams { + itemType: TItemType; + itemData: Partial; +} + +export const createItem = async (params: CreateItemParams): Promise => { + const { itemType, itemData } = params; + const { data } = await axiosRequester({ + options: { + method: "POST", + url: API_ENDPOINTS.ITEMS.CREATE_ITEM, + data: { + itemType, + ...itemData, + }, + }, + }); + + return data; +}; + +// 아이템 업데이트 +interface UpdateItemParams { + itemId: string; + itemData: Partial; +} + +export const updateItem = async (params: UpdateItemParams): Promise => { + const { itemId, itemData } = params; + const { data } = await axiosRequester({ + options: { + method: "PATCH", + url: API_ENDPOINTS.ITEMS.UPDATE_ITEM(itemId), + data: itemData, + }, + }); + + return data; +}; + +// 아이템 삭제 +export const deleteItem = async (itemId: string): Promise<{ message: string }> => { + const { data } = await axiosRequester<{ message: string }>({ + options: { + method: "DELETE", + url: API_ENDPOINTS.ITEMS.DELETE_ITEM(itemId), + }, + }); + + return data; +}; From ba36929f05396c7c26259f9a7b5bd73fdb38368a Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 16:04:45 +0900 Subject: [PATCH 04/72] =?UTF-8?q?Feat:=20meeting=20main=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=ED=9A=8C=EC=9D=98=EC=8B=A4,=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=EC=98=88=EC=95=BD=20=ED=98=84=ED=99=A9=20?= =?UTF-8?q?useQuery=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/meetings/_components/main.tsx | 80 +++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/main.tsx b/apps/web/app/meetings/_components/main.tsx index e1c70c9b..26c7c99a 100644 --- a/apps/web/app/meetings/_components/main.tsx +++ b/apps/web/app/meetings/_components/main.tsx @@ -1,6 +1,11 @@ "use client"; +import { useQuery } from "@tanstack/react-query"; +import { type IReservation } from "@repo/types/src/reservationType"; +import { type TBaseItem } from "@repo/types"; import { useDateStore } from "@/app/store/useDateStore"; +import { getReservationsByTypeAndDate } from "@/api/reservations"; +import { getAllItems } from "@/api/items"; import { rooms } from "../../mocks/mockData"; import ScheduleTable from "./Schedule/ScheduleTable"; @@ -9,5 +14,78 @@ export default function MeetingRoomSchedule(): JSX.Element { const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart(2, "0")}-${String(selectedDate.day).padStart(2, "0")}`; - return ; + const MeetingRoomsType = "room"; + + const { data: meetingsData, isLoading: meetingsIsLoading } = useQuery({ + queryKey: ["meetings", formattedDate, MeetingRoomsType], + queryFn: () => getReservationsByTypeAndDate({ itemType: MeetingRoomsType, date: formattedDate }), + }); + + const { data: RoomsData, isLoading: RoomsIsLoading } = useQuery({ + queryKey: ["Rooms", MeetingRoomsType], + queryFn: () => getAllItems({ itemType: MeetingRoomsType }), + }); + + if (meetingsIsLoading || RoomsIsLoading) return
로딩중이에요~
; + + return ( + <> + {" "} +
+ {meetingsData && meetingsData.length > 0 ? ( + meetingsData.map((meeting) => ( +
+

예약자: {meeting.user.name || "알 수 없음"}

+

회의실: {typeof meeting.item === "string" ? meeting.item : meeting.item.name || "알 수 없음"}

+

+ 시작 시간:{" "} + {new Date(meeting.startAt).toLocaleString("ko-KR", { + timeZone: "Asia/Seoul", + })} +

+

+ 종료 시간:{" "} + {new Date(meeting.endAt).toLocaleString("ko-KR", { + timeZone: "Asia/Seoul", + })} +

+

상태: {meeting.status}

+ {meeting.notes ?

메모: {meeting.notes}

: null} +
+ )) + ) : ( +

예약된 회의가 없습니다.{formattedDate}

+ )} +
+
+ {RoomsData && RoomsData.length > 0 ? ( + RoomsData.map((room) => ( +
+

회의실 이름: {room.name}

+

상태: {room.status}

+

설명: {room.description ?? "없음"}

+
+ )) + ) : ( +

등록된 회의실이 없습니다.

+ )} +
+ + ); } From a965e5e0997e6768296859d0dc237ec54be1df4f Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 16:40:32 +0900 Subject: [PATCH 05/72] =?UTF-8?q?Feat:=20ScheduleTable=20api=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Schedule/ScheduleTable/index.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx index 075de610..d38fe666 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/index.tsx @@ -1,18 +1,22 @@ "use client"; -import { type ScheduleDate } from "@/app/types/scheduletypes"; +import { type TBaseItem, type IReservation } from "@repo/types"; import ScheduleTableMobile from "./ScheduleTableMobile"; import ScheduleTableDesktop from "./ScheduleTableDesktop"; -type ScheduleTableProps = ScheduleDate; +interface ScheduleTableProps { + rooms: TBaseItem[]; + meetingsData: IReservation[]; + selectedDate: string; +} export default function ScheduleTable(props: ScheduleTableProps): JSX.Element { - const { rooms, selectedDate } = props; + const { rooms, meetingsData, selectedDate } = props; return (
- - + +
); } From edec6c407ef3c97c07510cd6335a5033a7c594a7 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 16:41:41 +0900 Subject: [PATCH 06/72] =?UTF-8?q?Feat:=20ScheduleTableDesktop=20api=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleTable/ScheduleTableDesktop.tsx | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index c2d08caa..04caef17 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -1,38 +1,53 @@ "use client"; -import { type ScheduleDate } from "@/app/types/scheduletypes"; +import { type TBaseItem, type IReservation } from "@repo/types"; import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import CurrentTimeIndicator from "../ScheduleRow/CurrentTimeIndicator"; import TimeText from "../ScheduleRow/TimeText"; -type ScheduleTableDesktopProps = ScheduleDate; +interface ScheduleTableDesktopProps { + rooms: TBaseItem[]; + meetingsData: IReservation[]; + selectedDate: string; +} export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps): JSX.Element { - const { rooms, selectedDate } = props; + const { rooms, meetingsData, selectedDate } = props; return (
{rooms.map((room) => ( -
- +
+
))}
- {rooms.map((room) => ( -
- schedule.date === selectedDate)} - slotWidth={72} - slotHeight={80} - room={room.title} - /> -
- ))} + {rooms.map((room) => { + // 해당 방의 스케줄 필터링 + const roomSchedules = meetingsData.filter((schedule) => { + const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; + + // 방 ID가 일치하는지 확인 + const isSameRoom = scheduleItemId === room._id; + + // 예약 날짜가 selectedDate와 같은지 확인 + const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; + const isSameDate = scheduleDate === selectedDate; + + return isSameRoom && isSameDate; + }); + + return ( +
+ +
+ ); + })}
From b699d6a0bdf3e4f64e3b0637bf7c67003a59f825 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 16:43:52 +0900 Subject: [PATCH 07/72] =?UTF-8?q?Feat:=20ScheduleTableMobile=20api=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleTable/ScheduleTableMobile.tsx | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx index 7763a47d..8b9b2d75 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx @@ -1,31 +1,49 @@ "use client"; -import { type ScheduleDate } from "@/app/types/scheduletypes"; +import { type TBaseItem } from "@repo/types"; +import { type IReservation } from "@repo/types/src/reservationType"; import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import TimeText from "../ScheduleRow/TimeText"; -type ScheduleTableMobileProps = ScheduleDate; +interface ScheduleTableMobileProps { + rooms: TBaseItem[]; + meetingsData: IReservation[]; + selectedDate: string; +} export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JSX.Element { - const { rooms, selectedDate } = props; + const { rooms, meetingsData, selectedDate } = props; return (
- {rooms.map((room) => ( -
- -
- -
- schedule.date === selectedDate)} - room={room.title} - /> + {rooms.map((room) => { + // 해당 방의 스케줄 필터링 + const roomSchedules = meetingsData.filter((schedule) => { + const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; + + // 방 ID가 일치하는지 확인 + const isSameRoom = scheduleItemId === room._id; + + // 예약 날짜가 selectedDate와 같은지 확인 + const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; + const isSameDate = scheduleDate === selectedDate; + + return isSameRoom && isSameDate; + }); + + return ( +
+ +
+ +
+ +
-
- ))} + ); + })}
); } From 5d172a9c36ab0beb6612024219f6e92cebae6a95 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 16:45:38 +0900 Subject: [PATCH 08/72] =?UTF-8?q?Feat:=20ScheduleRow=20api=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20time=20api=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Schedule/ScheduleRow/index.tsx | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index 94d8063a..bfe846ad 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -2,7 +2,7 @@ "use client"; import { useState } from "react"; -import { type Schedule } from "@/app/types/scheduletypes"; +import { type IReservation } from "@repo/types"; import { useSidebarStore } from "@/app/store/useSidebarStore"; import MobileReservationSheet from "../../Reservation/MobileReservationSheet"; import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; @@ -11,11 +11,11 @@ import ScheduleItem from "./ScheduleItem"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; interface ScheduleRowProps { - schedules: Schedule[]; + schedules: IReservation[]; room: string; slotWidth?: number; slotHeight?: number; - onSlotClick?: (time: string, schedule?: Schedule, room?: string) => void; + onSlotClick?: (time: string, schedule?: IReservation, room?: string) => void; } export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { @@ -27,22 +27,31 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const minutesPerSlot = 30; const totalMinutes = (endHour - startHour) * 60; - const timeToMinutes = (time: string): number => { - const [hoursStr, minutesStr] = time.split(":"); - const hours = Number(hoursStr); - const minutes = Number(minutesStr); + const timeToMinutes = (time: Date | string): number => { + let date: Date; + + if (typeof time === "string") { + date = new Date(time); + } else { + date = time; + } + + // 서버 시간대에 맞게 선택 (UTC 또는 로컬) + const hours = date.getUTCHours(); // 또는 date.getHours(); + const minutes = date.getUTCMinutes(); // 또는 date.getMinutes(); + return hours * 60 + minutes; }; const [selectedTime, setSelectedTime] = useState(null); - const [selectedSchedule, setSelectedSchedule] = useState(null); + const [selectedSchedule, setSelectedSchedule] = useState(null); const [selectedRoom, setSelectedRoom] = useState(room || null); const openSidebar = useSidebarStore((state) => state.openSidebar); const closeSidebar = useSidebarStore((state) => state.closeSidebar); const isSidebarOpen = useSidebarStore((state) => state.isSidebarOpen); - const handleSlotClick = (index: number, schedule?: Schedule): void => { + const handleSlotClick = (index: number, schedule?: IReservation): void => { const clickedTimeMinutes = startHour * 60 + index * minutesPerSlot; const hours = Math.floor(clickedTimeMinutes / 60); const minutes = clickedTimeMinutes % 60; @@ -82,8 +91,8 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element {
{schedules.map((schedule) => { - const startMinutes = timeToMinutes(schedule.start_time) - startHour * 60; - const endMinutes = timeToMinutes(schedule.end_time) - startHour * 60; + const startMinutes = timeToMinutes(schedule.startAt) - startHour * 60; + const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; const scheduleDuration = endMinutes - startMinutes; if (startMinutes < 0 || endMinutes > totalMinutes) return null; @@ -93,11 +102,11 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { return ( { handleSlotClick(-1, schedule); }} From 89256a22f9f765fda2623644250dfcba37b0a744 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 16:47:25 +0900 Subject: [PATCH 09/72] =?UTF-8?q?Feat:=20Tooltip=20props=20children?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Schedule/ScheduleRow/ScheduleTooltip.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx index 35f4b54f..0f5435aa 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleTooltip.tsx @@ -1,15 +1,17 @@ "use client"; +import { type ReactNode } from "react"; + interface ScheduleTooltipProps { - title: string; + children?: ReactNode; } export default function ScheduleTooltip(props: ScheduleTooltipProps): JSX.Element { - const { title } = props; + const { children } = props; return (
- {title} + {children}
From 5455e7404fedd9c5419f7d10e764750c5e8fa6eb Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 17:05:26 +0900 Subject: [PATCH 10/72] =?UTF-8?q?Feat:=20ScheduleItem=20type=20=EC=88=98?= =?UTF-8?q?=EC=A0=95(api=EC=97=90=20=EB=A7=9E=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Schedule/ScheduleRow/ScheduleItem.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx index 4a503750..2c696b36 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleItem.tsx @@ -2,11 +2,11 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ "use client"; -import { type Schedule } from "@/app/types/scheduletypes"; +import { type IReservation } from "@repo/types"; import ScheduleTooltip from "./ScheduleTooltip"; interface ScheduleItemProps { - schedule: Schedule; + schedule: IReservation; leftPosition: number; scheduleWidth: number; isCurrentUser: boolean; @@ -39,7 +39,7 @@ export default function ScheduleItem(props: ScheduleItemProps): JSX.Element { role="button" tabIndex={0} > - {isCurrentUser ? null : } + {isCurrentUser ? null : {schedule.notes}}
); From ff0a60312bb402b147db92432dd4afcb77f1612c Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 17:07:46 +0900 Subject: [PATCH 11/72] =?UTF-8?q?Feat:=20DesktopReservationSheet=20interfa?= =?UTF-8?q?ce=20=EC=88=98=EC=A0=95(api=EC=97=90=20=EB=A7=9E=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Reservation/DesktopReservationSheet.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index 6034b4ec..e8f77e95 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -2,6 +2,7 @@ "use client"; import { useEffect, useState } from "react"; +import { type IReservation } from "@repo/types"; import Sidebar from "@/components/common/Sidebar"; import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes"; import { useSidebarStore } from "@/app/store/useSidebarStore"; @@ -11,7 +12,7 @@ import ReservationModal from "./ReservationModal"; interface DesktopReservationSheetProps { onClose: () => void; selectedTime: string; - selectedSchedule?: Schedule | null; + selectedSchedule?: IReservation | null; selectedRoom: string; } From bcd7153d2a096ad7186a0c3c107870f496b06396 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 17:08:54 +0900 Subject: [PATCH 12/72] =?UTF-8?q?Feat:=20MobileReservationSheet=20interfac?= =?UTF-8?q?e=20=EC=88=98=EC=A0=95(api=EC=97=90=20=EB=A7=9E=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Reservation/MobileReservationSheet.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx index 2e6db1c0..89550266 100644 --- a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Sheet } from "react-modal-sheet"; import { useState } from "react"; +import { type IReservation } from "@repo/types"; import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes"; import ReservationForm from "./ReservationForm"; import ReservationModal from "./ReservationModal"; @@ -9,7 +10,7 @@ interface MobileReservationSheetProps { isOpen: boolean; onClose: () => void; selectedTime: string; - selectedSchedule?: Schedule | null; + selectedSchedule?: IReservation | null; selectedRoom: string; } From a31a9a554aee9a3449352ba7b8366a64fa1535b2 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 17:10:44 +0900 Subject: [PATCH 13/72] =?UTF-8?q?Feat:=20ReservationForm=20interface=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(api=EC=97=90=20=EB=A7=9E=EA=B2=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/meetings/_components/Reservation/ReservationForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index b69e3fca..dbfef936 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -6,6 +6,7 @@ import { useForm, Controller } from "react-hook-form"; import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; import { useEffect } from "react"; +import { type IReservation } from "@repo/types"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes"; @@ -13,7 +14,7 @@ import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes" interface ReservationFormProps { onSubmit: (data: ScheduleFormData) => void; selectedTime: string; - selectedSchedule?: Schedule | null; + selectedSchedule?: IReservation | null; resetTrigger?: number; selectedRoom?: string | null; } From 5b13d7d8a1352c60a14d99b3a4b4a41aa4b0e3bf Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 18:10:06 +0900 Subject: [PATCH 14/72] =?UTF-8?q?Chore:=20=EB=82=A0=EC=A7=9C=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20date-fns=20install?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/package.json b/apps/web/package.json index f83869f4..1d0c45de 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,7 @@ "@tanstack/react-query": "^5.59.0", "axios": "^1.7.7", "cookies-next": "^4.3.0", + "date-fns": "^4.1.0", "es-toolkit": "^1.26.1", "framer-motion": "^11.11.9", "next": "14.2.6", From 68b750991afd9410bc8474e381103fcd9e6b9e61 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 18:16:50 +0900 Subject: [PATCH 15/72] =?UTF-8?q?Feat:=20parseISO=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meetings/_components/Schedule/ScheduleRow/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index bfe846ad..58281dc7 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { type IReservation } from "@repo/types"; +import { parseISO } from "date-fns"; import { useSidebarStore } from "@/app/store/useSidebarStore"; import MobileReservationSheet from "../../Reservation/MobileReservationSheet"; import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; @@ -31,15 +32,13 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { let date: Date; if (typeof time === "string") { - date = new Date(time); + date = parseISO(time); } else { date = time; } - // 서버 시간대에 맞게 선택 (UTC 또는 로컬) - const hours = date.getUTCHours(); // 또는 date.getHours(); - const minutes = date.getUTCMinutes(); // 또는 date.getMinutes(); - + const hours = date.getHours(); + const minutes = date.getMinutes(); return hours * 60 + minutes; }; From f4fe529243de9425dc617781e9944517c70d7d91 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 18:34:27 +0900 Subject: [PATCH 16/72] =?UTF-8?q?Feat:=20=ED=98=84=EC=9E=AC=20userId?= =?UTF-8?q?=EC=99=80=20schedule=EC=9D=98=20userId=20=EB=B9=84=EA=B5=90?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=91=9C=ED=98=84(css=20=EB=B0=8F=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/meetings/_components/Schedule/ScheduleRow/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index 58281dc7..266bf268 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -10,6 +10,7 @@ import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; import ScheduleSlot from "./ScheduleSlot"; import ScheduleItem from "./ScheduleItem"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; +import { useAuthStore } from "@/src/stores/useAuthStore"; interface ScheduleRowProps { schedules: IReservation[]; @@ -22,6 +23,8 @@ interface ScheduleRowProps { export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const { schedules, room, slotHeight = 80, slotWidth = 72 } = props; + const user = useAuthStore((state) => state.user); + const startHour = 0; const endHour = 24; const totalSlots = (endHour - startHour) * 2; @@ -105,7 +108,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { schedule={schedule} leftPosition={leftPosition} scheduleWidth={scheduleWidth} - isCurrentUser={schedule._id === "1"} + isCurrentUser={schedule.user._id === user?._id} onClick={() => { handleSlotClick(-1, schedule); }} From 91538f8fa02fc5c105254ca7e0665b8e38ce86af Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 21:07:11 +0900 Subject: [PATCH 17/72] =?UTF-8?q?Feat:=20=EC=98=88=EC=95=BD=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EB=8A=94=20=ED=9A=8C=EC=9D=98=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EC=8B=9C=20=EB=82=B4=EC=9A=A9=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 2 +- .../Reservation/MobileReservationSheet.tsx | 2 +- .../Reservation/ReservationForm.tsx | 70 +++++++++++-------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index e8f77e95..5718fd76 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -13,7 +13,7 @@ interface DesktopReservationSheetProps { onClose: () => void; selectedTime: string; selectedSchedule?: IReservation | null; - selectedRoom: string; + selectedRoom?: string | null; } export default function DesktopReservationSheet(props: DesktopReservationSheetProps): JSX.Element { diff --git a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx index 89550266..dbd8d467 100644 --- a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx @@ -11,7 +11,7 @@ interface MobileReservationSheetProps { onClose: () => void; selectedTime: string; selectedSchedule?: IReservation | null; - selectedRoom: string; + selectedRoom?: string | null; } export default function MobileReservationSheet({ diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index dbfef936..1b9ceb6f 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -7,9 +7,10 @@ import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; import { useEffect } from "react"; import { type IReservation } from "@repo/types"; +import { format } from "date-fns"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; -import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes"; +import { type ScheduleFormData } from "@/app/types/scheduletypes"; interface ReservationFormProps { onSubmit: (data: ScheduleFormData) => void; @@ -43,7 +44,18 @@ const addMinutes = (time: string, minutesToAdd: number): string => { }; export default function ReservationForm(props: ReservationFormProps): JSX.Element { - const { onSubmit, selectedTime, resetTrigger, selectedRoom } = props; + const { onSubmit, selectedTime, resetTrigger, selectedRoom, selectedSchedule } = props; + + // 폼의 기본 값을 설정합니다. + const defaultValues: ScheduleFormData = { + meetingTitle: selectedSchedule?.notes ?? "", + selectedRoom: selectedRoom ?? "", + startTime: selectedSchedule ? format(new Date(selectedSchedule.startAt), "HH:mm") : selectedTime, + customStartTime: "", + endTime: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : "", + customEndTime: "", + participants: [], + }; const { control, @@ -55,15 +67,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen setValue, clearErrors, } = useForm({ - defaultValues: { - meetingTitle: "", - selectedRoom: selectedRoom ?? "", - startTime: selectedTime, - customStartTime: "", - endTime: "", - customEndTime: "", - participants: [], - }, + defaultValues, }); const rooms = ["미팅룸 A", "미팅룸 B", "미팅룸 C", "미팅룸 D", "미팅룸 E", "녹음실 A", "녹음실 B", "녹음실 C"]; @@ -85,18 +89,10 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const endTimeValue = watch("endTime"); const participantsSelected = watch("participants").length > 0; - // resetTrigger가 변경될 때마다 폼을 리셋 + // resetTrigger 또는 selectedSchedule이 변경될 때마다 폼을 리셋 useEffect(() => { - reset({ - meetingTitle: "", - selectedRoom: selectedRoom ?? "", // selectedRoom 포함 - startTime: selectedTime, - customStartTime: "", - endTime: "", - customEndTime: "", - participants: [], - }); - }, [resetTrigger, reset, selectedTime, selectedRoom]); + reset(defaultValues); + }, [resetTrigger, reset, selectedTime, selectedRoom, selectedSchedule]); // startTime 또는 customStartTime이 변경될 때 endTime을 설정 useEffect(() => { @@ -113,7 +109,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const newEndTime = addMinutes(currentStartTime, 30); setValue("endTime", newEndTime, { shouldValidate: true }); } catch (error) { - null; + // 에러 처리 + console.error(error); } } }, [startTimeValue, customStartTimeValue, setValue, selectedTime]); @@ -169,8 +166,6 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen > {field.value || "회의실 선택"} - {" "} - {/* 스크롤 추가 */} {rooms.map((room) => ( {room} @@ -199,8 +194,6 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen > {field.value || selectedTime} - {" "} - {/* 스크롤 추가 */} 직접입력 {timeOptions.map((time) => ( @@ -284,8 +277,6 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen )} - {" "} - {/* 스크롤 추가 */} {mockParticipants .sort((a, b) => { const isASelected = field.value.includes(a); @@ -314,6 +305,27 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen > 예약하기 + {/* 예약 정보 표시 */} + {selectedSchedule ? ( +
+

예약 정보

+

+ 회의 제목: {selectedSchedule.notes} +

+

+ 회의실: {selectedSchedule._id} +

+

+ 시작 시간: {format(new Date(selectedSchedule.startAt), "yyyy-MM-dd HH:mm")} +

+

+ 종료 시간: {format(new Date(selectedSchedule.endAt), "yyyy-MM-dd HH:mm")} +

+

+ 참여자: +

+
+ ) : null}
); } From 36d19c488f417593ea8f50ca7e9d8853b3d2f547 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 14 Nov 2024 21:08:17 +0900 Subject: [PATCH 18/72] =?UTF-8?q?Chore:=20query=20data,=20loading=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/meetings/_components/main.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/web/app/meetings/_components/main.tsx b/apps/web/app/meetings/_components/main.tsx index 26c7c99a..782e695e 100644 --- a/apps/web/app/meetings/_components/main.tsx +++ b/apps/web/app/meetings/_components/main.tsx @@ -6,33 +6,35 @@ import { type TBaseItem } from "@repo/types"; import { useDateStore } from "@/app/store/useDateStore"; import { getReservationsByTypeAndDate } from "@/api/reservations"; import { getAllItems } from "@/api/items"; -import { rooms } from "../../mocks/mockData"; import ScheduleTable from "./Schedule/ScheduleTable"; export default function MeetingRoomSchedule(): JSX.Element { const { selectedDate } = useDateStore(); - const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart(2, "0")}-${String(selectedDate.day).padStart(2, "0")}`; + const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart( + 2, + "0", + )}-${String(selectedDate.day).padStart(2, "0")}`; const MeetingRoomsType = "room"; - const { data: meetingsData, isLoading: meetingsIsLoading } = useQuery({ + const { data: meetingsData = [], isLoading: meetingsIsLoading } = useQuery({ queryKey: ["meetings", formattedDate, MeetingRoomsType], queryFn: () => getReservationsByTypeAndDate({ itemType: MeetingRoomsType, date: formattedDate }), }); - const { data: RoomsData, isLoading: RoomsIsLoading } = useQuery({ + const { data: roomsData = [], isLoading: roomsIsLoading } = useQuery({ queryKey: ["Rooms", MeetingRoomsType], queryFn: () => getAllItems({ itemType: MeetingRoomsType }), }); - if (meetingsIsLoading || RoomsIsLoading) return
로딩중이에요~
; + if (meetingsIsLoading || roomsIsLoading) return
로딩중이에요~
; return ( <> - {" "} +
- {meetingsData && meetingsData.length > 0 ? ( + {meetingsData.length > 0 ? ( meetingsData.map((meeting) => (
)) ) : ( -

예약된 회의가 없습니다.{formattedDate}

+

예약된 회의가 없습니다. {formattedDate}

)}
- {RoomsData && RoomsData.length > 0 ? ( - RoomsData.map((room) => ( + {roomsData.length > 0 ? ( + roomsData.map((room) => (
Date: Thu, 14 Nov 2024 21:09:19 +0900 Subject: [PATCH 19/72] =?UTF-8?q?Fix:=20lint=20error(import=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/meetings/_components/Schedule/ScheduleRow/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index 266bf268..0d2d83a7 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -5,12 +5,12 @@ import { useState } from "react"; import { type IReservation } from "@repo/types"; import { parseISO } from "date-fns"; import { useSidebarStore } from "@/app/store/useSidebarStore"; +import { useAuthStore } from "@/src/stores/useAuthStore"; import MobileReservationSheet from "../../Reservation/MobileReservationSheet"; import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; import ScheduleSlot from "./ScheduleSlot"; import ScheduleItem from "./ScheduleItem"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; -import { useAuthStore } from "@/src/stores/useAuthStore"; interface ScheduleRowProps { schedules: IReservation[]; @@ -23,6 +23,7 @@ interface ScheduleRowProps { export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const { schedules, room, slotHeight = 80, slotWidth = 72 } = props; + // 현재 로그인된 사용자 정보 가져오기 const user = useAuthStore((state) => state.user); const startHour = 0; From 148782a86d90b064fc8b763d459bdb846af1e42b Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 10:40:27 +0900 Subject: [PATCH 20/72] =?UTF-8?q?Feat:=20scheduletype=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EB=B0=8F=20room=20=5Fid=20props=EB=A1=9C=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 4 +-- .../Reservation/MobileReservationSheet.tsx | 4 +-- .../Schedule/ScheduleRow/index.tsx | 27 +++++++++---------- .../ScheduleTable/ScheduleTableDesktop.tsx | 10 ++++--- .../ScheduleTable/ScheduleTableMobile.tsx | 12 ++++++--- apps/web/app/types/scheduletypes.ts | 6 ++++- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index 5718fd76..beac2942 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from "react"; import { type IReservation } from "@repo/types"; import Sidebar from "@/components/common/Sidebar"; -import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes"; +import { type ScheduleFormData, type Schedule, type SelectedRoom } from "@/app/types/scheduletypes"; import { useSidebarStore } from "@/app/store/useSidebarStore"; import ReservationForm from "./ReservationForm"; import ReservationModal from "./ReservationModal"; @@ -13,7 +13,7 @@ interface DesktopReservationSheetProps { onClose: () => void; selectedTime: string; selectedSchedule?: IReservation | null; - selectedRoom?: string | null; + selectedRoom?: SelectedRoom | null; } export default function DesktopReservationSheet(props: DesktopReservationSheetProps): JSX.Element { diff --git a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx index dbd8d467..7b00fcdb 100644 --- a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx @@ -2,7 +2,7 @@ import { Sheet } from "react-modal-sheet"; import { useState } from "react"; import { type IReservation } from "@repo/types"; -import { type ScheduleFormData, type Schedule } from "@/app/types/scheduletypes"; +import { type ScheduleFormData, type Schedule, SelectedRoom } from "@/app/types/scheduletypes"; import ReservationForm from "./ReservationForm"; import ReservationModal from "./ReservationModal"; @@ -11,7 +11,7 @@ interface MobileReservationSheetProps { onClose: () => void; selectedTime: string; selectedSchedule?: IReservation | null; - selectedRoom?: string | null; + selectedRoom?: SelectedRoom | null; } export default function MobileReservationSheet({ diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index 0d2d83a7..f666dac2 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -11,21 +11,28 @@ import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; import ScheduleSlot from "./ScheduleSlot"; import ScheduleItem from "./ScheduleItem"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; +import { SelectedRoom } from "@/app/types/scheduletypes"; interface ScheduleRowProps { schedules: IReservation[]; - room: string; + room: SelectedRoom; slotWidth?: number; slotHeight?: number; - onSlotClick?: (time: string, schedule?: IReservation, room?: string) => void; + onSlotClick?: (time: string, schedule?: IReservation, room?: { name: string; _id: string }) => void; } - export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const { schedules, room, slotHeight = 80, slotWidth = 72 } = props; - // 현재 로그인된 사용자 정보 가져오기 const user = useAuthStore((state) => state.user); + const [selectedTime, setSelectedTime] = useState(null); + const [selectedSchedule, setSelectedSchedule] = useState(null); + const [selectedRoom, setSelectedRoom] = useState(room || null); + + const openSidebar = useSidebarStore((state) => state.openSidebar); + const closeSidebar = useSidebarStore((state) => state.closeSidebar); + const isSidebarOpen = useSidebarStore((state) => state.isSidebarOpen); + const startHour = 0; const endHour = 24; const totalSlots = (endHour - startHour) * 2; @@ -46,14 +53,6 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { return hours * 60 + minutes; }; - const [selectedTime, setSelectedTime] = useState(null); - const [selectedSchedule, setSelectedSchedule] = useState(null); - const [selectedRoom, setSelectedRoom] = useState(room || null); - - const openSidebar = useSidebarStore((state) => state.openSidebar); - const closeSidebar = useSidebarStore((state) => state.closeSidebar); - const isSidebarOpen = useSidebarStore((state) => state.isSidebarOpen); - const handleSlotClick = (index: number, schedule?: IReservation): void => { const clickedTimeMinutes = startHour * 60 + index * minutesPerSlot; const hours = Math.floor(clickedTimeMinutes / 60); @@ -124,7 +123,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { onClose={handleClose} selectedTime={selectedTime} selectedSchedule={selectedSchedule} - selectedRoom={selectedRoom} + selectedRoom={room} />
@@ -134,7 +133,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { onClose={handleClose} selectedTime={selectedTime} selectedSchedule={selectedSchedule} - selectedRoom={selectedRoom} + selectedRoom={room} />
diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index 04caef17..130354a5 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -28,14 +28,11 @@ export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps):
{rooms.map((room) => { - // 해당 방의 스케줄 필터링 const roomSchedules = meetingsData.filter((schedule) => { const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; - // 방 ID가 일치하는지 확인 const isSameRoom = scheduleItemId === room._id; - // 예약 날짜가 selectedDate와 같은지 확인 const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; const isSameDate = scheduleDate === selectedDate; @@ -44,7 +41,12 @@ export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps): return (
- +
); })} diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx index 8b9b2d75..e4a277c9 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx @@ -12,20 +12,19 @@ interface ScheduleTableMobileProps { selectedDate: string; } +// ScheduleTableMobile.tsx + export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JSX.Element { const { rooms, meetingsData, selectedDate } = props; return (
{rooms.map((room) => { - // 해당 방의 스케줄 필터링 const roomSchedules = meetingsData.filter((schedule) => { const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; - // 방 ID가 일치하는지 확인 const isSameRoom = scheduleItemId === room._id; - // 예약 날짜가 selectedDate와 같은지 확인 const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; const isSameDate = scheduleDate === selectedDate; @@ -38,7 +37,12 @@ export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JS
- +
diff --git a/apps/web/app/types/scheduletypes.ts b/apps/web/app/types/scheduletypes.ts index 3140a406..7872eae7 100644 --- a/apps/web/app/types/scheduletypes.ts +++ b/apps/web/app/types/scheduletypes.ts @@ -19,10 +19,14 @@ export interface ScheduleDate { export interface ScheduleFormData { meetingTitle: string; - selectedRoom: string; + selectedRoom: SelectedRoom | null; // name과 _id를 포함 startTime: string; customStartTime: string; endTime: string; customEndTime: string; participants: string[]; } +export interface SelectedRoom { + name: string; + _id: string; +} From 741598798394b7276d3aff775369f21e52332013 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 11:04:10 +0900 Subject: [PATCH 21/72] =?UTF-8?q?Feat:=20ReservationForm=20rooms=20query?= =?UTF-8?q?=EB=A1=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/MobileReservationSheet.tsx | 2 +- .../Reservation/ReservationForm.tsx | 44 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx index 7b00fcdb..20ca17cd 100644 --- a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx @@ -2,7 +2,7 @@ import { Sheet } from "react-modal-sheet"; import { useState } from "react"; import { type IReservation } from "@repo/types"; -import { type ScheduleFormData, type Schedule, SelectedRoom } from "@/app/types/scheduletypes"; +import { type ScheduleFormData, type Schedule, type SelectedRoom } from "@/app/types/scheduletypes"; import ReservationForm from "./ReservationForm"; import ReservationModal from "./ReservationModal"; diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 1b9ceb6f..5dce6059 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -6,18 +6,22 @@ import { useForm, Controller } from "react-hook-form"; import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; import { useEffect } from "react"; -import { type IReservation } from "@repo/types"; +import { type TBaseItem, type IReservation } from "@repo/types"; import { format } from "date-fns"; +import { useQuery } from "@tanstack/react-query"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; -import { type ScheduleFormData } from "@/app/types/scheduletypes"; +import { type SelectedRoom, type ScheduleFormData } from "@/app/types/scheduletypes"; +import { getAllItems } from "@/api/items"; + +// ReservationForm.tsx interface ReservationFormProps { onSubmit: (data: ScheduleFormData) => void; selectedTime: string; selectedSchedule?: IReservation | null; resetTrigger?: number; - selectedRoom?: string | null; + selectedRoom?: SelectedRoom | null; // name과 _id를 포함 } const addMinutes = (time: string, minutesToAdd: number): string => { @@ -46,10 +50,19 @@ const addMinutes = (time: string, minutesToAdd: number): string => { export default function ReservationForm(props: ReservationFormProps): JSX.Element { const { onSubmit, selectedTime, resetTrigger, selectedRoom, selectedSchedule } = props; + const MeetingRoomsType = "room"; + + const { data: roomsData = [], isLoading: roomsIsLoading } = useQuery({ + queryKey: ["Rooms", MeetingRoomsType], + queryFn: () => getAllItems({ itemType: MeetingRoomsType }), + }); + + if (roomsIsLoading) return
로딩중이에요~
; + // 폼의 기본 값을 설정합니다. const defaultValues: ScheduleFormData = { meetingTitle: selectedSchedule?.notes ?? "", - selectedRoom: selectedRoom ?? "", + selectedRoom: selectedRoom ?? null, startTime: selectedSchedule ? format(new Date(selectedSchedule.startAt), "HH:mm") : selectedTime, customStartTime: "", endTime: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : "", @@ -70,7 +83,6 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen defaultValues, }); - const rooms = ["미팅룸 A", "미팅룸 B", "미팅룸 C", "미팅룸 D", "미팅룸 E", "녹음실 A", "녹음실 B", "녹음실 C"]; const mockParticipants = [ "배영준", "조현지", @@ -157,18 +169,23 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen rules={{ required: "회의실을 선택해주세요." }} render={({ field }) => ( { - field.onChange(value); + if (typeof value === "string") { + const selected = roomsData.find((room) => room.name === value); + if (selected) { + field.onChange(selected); // 전체 객체를 저장 + } + } }} isError={Boolean(errors.selectedRoom)} errorMessage={errors.selectedRoom?.message ?? ""} > - {field.value || "회의실 선택"} + {field.value ? field.value.name : "회의실 선택"} - {rooms.map((room) => ( - - {room} + {roomsData.map((room) => ( + + {room.name} ))} @@ -324,6 +341,11 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen

참여자:

+ {watch("selectedRoom") && ( +
+ Room ID: {watch("selectedRoom")?._id} +
+ )}
) : null}
From bd4251d59b15260e9cb06a3449f662ede00695c2 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 11:27:13 +0900 Subject: [PATCH 22/72] =?UTF-8?q?Feat:=20getAllUser=20API=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/api/users.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/web/api/users.ts b/apps/web/api/users.ts index 7de6c2e5..acce6d59 100644 --- a/apps/web/api/users.ts +++ b/apps/web/api/users.ts @@ -12,3 +12,14 @@ export const getUser = async (): Promise => { return data; }; + +export const getAllUser = async (): Promise => { + const { data } = await axiosRequester({ + options: { + method: "GET", + url: API_ENDPOINTS.USERS.GET_ALL, + }, + }); + + return data; +}; From a06463d792dfcd491de3d9ab1d116b4e970497cf Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 11:28:48 +0900 Subject: [PATCH 23/72] =?UTF-8?q?Feat:=20getAllUser=20query=EB=A1=9C=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 5dce6059..33678498 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -6,15 +6,14 @@ import { useForm, Controller } from "react-hook-form"; import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; import { useEffect } from "react"; -import { type TBaseItem, type IReservation } from "@repo/types"; +import { type TBaseItem, type IReservation, type IUser } from "@repo/types"; import { format } from "date-fns"; import { useQuery } from "@tanstack/react-query"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; import { type SelectedRoom, type ScheduleFormData } from "@/app/types/scheduletypes"; import { getAllItems } from "@/api/items"; - -// ReservationForm.tsx +import { getAllUser } from "@/api/users"; // 사용자 데이터 가져오는 함수 interface ReservationFormProps { onSubmit: (data: ScheduleFormData) => void; @@ -52,12 +51,24 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const MeetingRoomsType = "room"; - const { data: roomsData = [], isLoading: roomsIsLoading } = useQuery({ + // 방 데이터를 useQuery로 가져오기 + const { + data: roomsData = [], + isLoading: roomsIsLoading, + isError: roomsIsError, + } = useQuery({ queryKey: ["Rooms", MeetingRoomsType], queryFn: () => getAllItems({ itemType: MeetingRoomsType }), }); - if (roomsIsLoading) return
로딩중이에요~
; + const { + data: allUsersData = [], + isLoading: allUsersIsLoading, + isError: allUsersIsError, + } = useQuery({ + queryKey: ["AllUsers"], + queryFn: getAllUser, + }); // 폼의 기본 값을 설정합니다. const defaultValues: ScheduleFormData = { @@ -127,6 +138,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen } }, [startTimeValue, customStartTimeValue, setValue, selectedTime]); + // 시작 시간과 종료 시간을 비교하여 유효성 검사 useEffect(() => { const compareTimes = (start: string, end: string): boolean => { const [startHour = 0, startMinute = 0] = start.split(":").map((value) => { @@ -162,6 +174,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen rules={{ required: "미팅 제목을 입력해주세요." }} render={({ field }) => } /> + {/* 미팅룸 선택 */} {field.value ? field.value.name : "회의실 선택"} - {roomsData.map((room) => ( - - {room.name} - - ))} + {roomsIsLoading ? ( +
회의실 로딩 중...
+ ) : roomsIsError ? ( +
회의실 정보를 불러오는 데 실패했습니다.
+ ) : ( + roomsData.map((room) => ( + + {room.name} + + )) + )}
)} /> + + {/* 선택된 방의 ID를 별도의 div에 표시 */} +
+ {watch("selectedRoom") && ( +
+ Room ID: {watch("selectedRoom")?._id} +
+ )} +
+ {/* 시작 시간 및 종료 시간 선택 */}
{/* 시작 시간 */} @@ -268,6 +297,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen )}
+ {/* 참여자 선택 */} )} /> + + {/* 예약 정보 표시 */} {selectedSchedule ? (
@@ -341,11 +373,6 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen

참여자:

- {watch("selectedRoom") && ( -
- Room ID: {watch("selectedRoom")?._id} -
- )}
) : null}
From 9adf451c56ac67c721ebda0961746858a1fa1b6b Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 11:46:39 +0900 Subject: [PATCH 24/72] =?UTF-8?q?Feat:=20=EC=A0=84=EC=B2=B4=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20Dropdown=20=EC=84=A0=ED=83=9D=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 33678498..95f9b269 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -11,9 +11,10 @@ import { format } from "date-fns"; import { useQuery } from "@tanstack/react-query"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; +import { Badge } from "@ui/index"; import { type SelectedRoom, type ScheduleFormData } from "@/app/types/scheduletypes"; import { getAllItems } from "@/api/items"; -import { getAllUser } from "@/api/users"; // 사용자 데이터 가져오는 함수 +import { getAllUser } from "@/api/users"; // 전체 사용자 데이터 가져오는 함수 interface ReservationFormProps { onSubmit: (data: ScheduleFormData) => void; @@ -61,6 +62,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen queryFn: () => getAllItems({ itemType: MeetingRoomsType }), }); + // 전체 사용자 데이터를 useQuery로 가져오기 const { data: allUsersData = [], isLoading: allUsersIsLoading, @@ -94,18 +96,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen defaultValues, }); - const mockParticipants = [ - "배영준", - "조현지", - "김보경", - "신승헌", - "소혜린", - "이대양", - "이영훈", - "이정민", - "이지현", - "천권희", - ]; + // 실제 사용자 목록을 participants 드롭다운에 적용합니다. + const participantsOptions = allUsersData.map((user) => user.name); const startTimeValue = watch("startTime"); const customStartTimeValue = watch("customStartTime"); @@ -312,9 +304,14 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen {field.value.length > 0 ? (
- {field.value.slice(0, 3).map((name) => ( - - ))} + {field.value.slice(0, 3).map((name) => { + const user = allUsersData.find((user) => user.name === name); + return user ? ( +
+ +
+ ) : null; + })} {field.value.length > 3 && ( +{field.value.length - 3}명 )} @@ -324,20 +321,30 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen )} - {mockParticipants - .sort((a, b) => { - const isASelected = field.value.includes(a); - const isBSelected = field.value.includes(b); - - if (isASelected && !isBSelected) return -1; - if (!isASelected && isBSelected) return 1; - return 0; - }) - .map((name) => ( - - + {allUsersIsLoading ? ( +
사용자 데이터 로딩 중...
+ ) : allUsersIsError ? ( +
사용자 데이터를 불러오는 데 실패했습니다.
+ ) : allUsersData.length === 0 ? ( +
참여자가 없습니다.
+ ) : ( + allUsersData.map((user) => ( + +
+ +
+
+ {user.teams.map((team, index) => ( + + {team} + + ))} +
+
+
- ))} + )) + )}
)} From 8741df0e5e3eafc12ee2829c26fb95398618dee1 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 15:16:02 +0900 Subject: [PATCH 25/72] Fix: Dropdown storybook state type error --- packages/ui/src/components/common/Dropdown/index.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/common/Dropdown/index.stories.tsx b/packages/ui/src/components/common/Dropdown/index.stories.tsx index 55da4852..422aecef 100644 --- a/packages/ui/src/components/common/Dropdown/index.stories.tsx +++ b/packages/ui/src/components/common/Dropdown/index.stories.tsx @@ -13,10 +13,10 @@ type Story = StoryObj; export const Default: Story = { render: () => { - const [selectedValue, setSelectedValue] = useState(""); + const [selectedValue, setSelectedValue] = useState(""); return ( - setSelectedValue(value)}> + setSelectedValue(_value)}> 최신순 아이템 1 From 1a61069f147e662019051ec58bbf6bc37ca37fb9 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 15:18:22 +0900 Subject: [PATCH 26/72] =?UTF-8?q?Feat:=20MultiSelectDropdown=20wrapper=20c?= =?UTF-8?q?lassName=20props=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/Dropdown/MulitiSelectDropdown.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/common/Dropdown/MulitiSelectDropdown.tsx b/packages/ui/src/components/common/Dropdown/MulitiSelectDropdown.tsx index f4efd3e6..f5096ca6 100644 --- a/packages/ui/src/components/common/Dropdown/MulitiSelectDropdown.tsx +++ b/packages/ui/src/components/common/Dropdown/MulitiSelectDropdown.tsx @@ -153,9 +153,10 @@ function Toggle({ children, title }: ToggleProps): JSX.Element { interface WrapperProps { children: ReactNode; + className?: string; } -function Wrapper({ children }: WrapperProps): JSX.Element { +function Wrapper({ children, className }: WrapperProps): JSX.Element { const { isOpen, searchTerm, setSearchTerm } = useContext(DropdownContext); const filteredChildren = @@ -177,6 +178,7 @@ function Wrapper({ children }: WrapperProps): JSX.Element { Date: Mon, 18 Nov 2024 15:38:03 +0900 Subject: [PATCH 27/72] =?UTF-8?q?Style:=20Profile=20className=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(=EC=A0=84=EC=97=AD=EC=9C=BC=EB=A1=9C=20h-auto=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84=ED=95=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/components/common/Profile/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/components/common/Profile/index.tsx b/apps/web/components/common/Profile/index.tsx index 42beec5d..162a8d9e 100644 --- a/apps/web/components/common/Profile/index.tsx +++ b/apps/web/components/common/Profile/index.tsx @@ -32,7 +32,7 @@ function Profile({ src, name, className, textColor = "white" }: ProfileProps): J src={src} width={32} height={32} - style={{ borderRadius: 9999 }} + className="h-32 w-32 rounded-full object-cover" onError={() => { setIsError(true); }} From 5e9b927fc788d41d6c6a7841a315bc7a408d4c10 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 18:02:20 +0900 Subject: [PATCH 28/72] =?UTF-8?q?Feat:=20response=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A1=B0=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 317 +++++++----------- 1 file changed, 129 insertions(+), 188 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 95f9b269..1ae2ac07 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -5,53 +5,38 @@ import Dropdown from "@ui/src/components/common/Dropdown"; import { useForm, Controller } from "react-hook-form"; import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { type TBaseItem, type IReservation, type IUser } from "@repo/types"; import { format } from "date-fns"; -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Badge, notify } from "@ui/index"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; -import { Badge } from "@ui/index"; import { type SelectedRoom, type ScheduleFormData } from "@/app/types/scheduletypes"; +import { getAllUser } from "@/api/users"; import { getAllItems } from "@/api/items"; -import { getAllUser } from "@/api/users"; // 전체 사용자 데이터 가져오는 함수 +import { useAuthStore } from "@/src/stores/useAuthStore"; +import { createReservation, CreateReservationRequest } from "@/api/reservations"; +import { useDateStore } from "@/app/store/useDateStore"; interface ReservationFormProps { - onSubmit: (data: ScheduleFormData) => void; + onSubmit: (data: CreateReservationRequest, itemId: string) => void; // itemId 파라미터 추가 selectedTime: string; selectedSchedule?: IReservation | null; resetTrigger?: number; - selectedRoom?: SelectedRoom | null; // name과 _id를 포함 + selectedRoom?: SelectedRoom | null; } -const addMinutes = (time: string, minutesToAdd: number): string => { - const parts = time.split(":"); - - if (parts.length !== 2) { - throw new Error("Invalid time format. Expected format HH:MM."); - } - - const [hoursStr, minutesStr] = parts; - const hours = Number(hoursStr); - const minutes = Number(minutesStr); - - if (isNaN(hours) || isNaN(minutes)) { - throw new Error("Invalid time format. Hours and minutes must be numbers."); - } - - const totalMinutes = hours * 60 + minutes + minutesToAdd; - const newHours = Math.floor(totalMinutes / 60) % 24; - const newMinutes = totalMinutes % 60; - const formattedHours = newHours.toString().padStart(2, "0"); - const formattedMinutes = newMinutes.toString().padStart(2, "0"); - return `${formattedHours}:${formattedMinutes}`; -}; - export default function ReservationForm(props: ReservationFormProps): JSX.Element { const { onSubmit, selectedTime, resetTrigger, selectedRoom, selectedSchedule } = props; const MeetingRoomsType = "room"; + // useAuthStore를 사용하여 현재 사용자 데이터 가져오기 + const user = useAuthStore((state) => state.user); // 현재 사용자의 데이터 가져오기 + + const { selectedDate } = useDateStore(); + // 방 데이터를 useQuery로 가져오기 const { data: roomsData = [], @@ -73,14 +58,14 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen }); // 폼의 기본 값을 설정합니다. - const defaultValues: ScheduleFormData = { - meetingTitle: selectedSchedule?.notes ?? "", - selectedRoom: selectedRoom ?? null, - startTime: selectedSchedule ? format(new Date(selectedSchedule.startAt), "HH:mm") : selectedTime, - customStartTime: "", - endTime: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : "", - customEndTime: "", - participants: [], + const defaultValues: CreateReservationRequest = { + userId: user!._id, + itemType: "room", + notes: selectedSchedule?.notes ?? "", + startAt: selectedSchedule ? format(new Date(selectedSchedule.startAt), "HH:mm") : selectedTime, + endAt: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : "", + status: "reserved", + attendees: [], }; const { @@ -92,17 +77,25 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen setError, setValue, clearErrors, - } = useForm({ + } = useForm({ defaultValues, }); - // 실제 사용자 목록을 participants 드롭다운에 적용합니다. - const participantsOptions = allUsersData.map((user) => user.name); + // 상태를 추가하여 제출된 데이터를 저장 + const [submittedData, setSubmittedData] = useState<{ + userId: string; + itemType: string; + startAt: string; + endAt: string; + status: string; + notes: string; + attendees: string[]; // 사용자 ID 배열로 변경 + } | null>(null); + + // ReservationForm 컴포넌트 내 + const [selectedMeetingRoom, setSelectedMeetingRoom] = useState(selectedRoom); - const startTimeValue = watch("startTime"); - const customStartTimeValue = watch("customStartTime"); - const endTimeValue = watch("endTime"); - const participantsSelected = watch("participants").length > 0; + const attendeessSelected = watch("attendees").length > 0; // resetTrigger 또는 selectedSchedule이 변경될 때마다 폼을 리셋 useEffect(() => { @@ -110,115 +103,85 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen }, [resetTrigger, reset, selectedTime, selectedRoom, selectedSchedule]); // startTime 또는 customStartTime이 변경될 때 endTime을 설정 - useEffect(() => { - let currentStartTime = selectedTime; - if (startTimeValue === "custom-start" && customStartTimeValue) { - currentStartTime = customStartTimeValue; - } else if (startTimeValue && startTimeValue !== "custom-start") { - currentStartTime = startTimeValue; - } + // 시작 시간과 종료 시간을 비교하여 유효성 검사 - if (currentStartTime) { - try { - const newEndTime = addMinutes(currentStartTime, 30); - setValue("endTime", newEndTime, { shouldValidate: true }); - } catch (error) { - // 에러 처리 - console.error(error); - } + const onFormSubmit = (data: CreateReservationRequest): void => { + if (!user || !selectedRoom?._id) { + return; } - }, [startTimeValue, customStartTimeValue, setValue, selectedTime]); - // 시작 시간과 종료 시간을 비교하여 유효성 검사 - useEffect(() => { - const compareTimes = (start: string, end: string): boolean => { - const [startHour = 0, startMinute = 0] = start.split(":").map((value) => { - const num = parseInt(value, 10); - return isNaN(num) ? 0 : num; - }); + const attendeeIds = data.attendees + .map((name: string) => { + const selectedUser = allUsersData.find((user) => user.name === name); + return selectedUser ? selectedUser._id : null; + }) + .filter((id): id is string => id !== null); - const [endHour = 0, endMinute = 0] = end.split(":").map((value) => { - const num = parseInt(value, 10); - return isNaN(num) ? 0 : num; - }); + const { year, month, day } = selectedDate; - return startHour > endHour || (startHour === endHour && startMinute >= endMinute); + const startAt = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.startAt}:00`; + const endAt = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.endAt}:00`; + + const mappedData: CreateReservationRequest = { + userId: user._id, + itemType: "room", + startAt, + endAt, + status: "reserved", + notes: data.notes, + attendees: attendeeIds, }; - const currentStartTime = startTimeValue === "custom-start" ? customStartTimeValue : startTimeValue; - if (currentStartTime && endTimeValue && compareTimes(currentStartTime, endTimeValue)) { - setError("endTime", { - type: "manual", - message: "종료 시간은 시작 시간보다 이후여야 합니다.", - }); - } else { - clearErrors("endTime"); - } - }, [startTimeValue, customStartTimeValue, endTimeValue, setError, clearErrors]); + // 상태에 저장하여 화면에 표시 + setSubmittedData(mappedData); + + onSubmit(mappedData, selectedMeetingRoom!._id); + }; return (
{/* 미팅 제목 입력 */} } /> - {/* 미팅룸 선택 */} - ( - { - if (typeof value === "string") { - const selected = roomsData.find((room) => room.name === value); - if (selected) { - field.onChange(selected); // 전체 객체를 저장 - } - } - }} - isError={Boolean(errors.selectedRoom)} - errorMessage={errors.selectedRoom?.message ?? ""} - > - {field.value ? field.value.name : "회의실 선택"} - - {roomsIsLoading ? ( -
회의실 로딩 중...
- ) : roomsIsError ? ( -
회의실 정보를 불러오는 데 실패했습니다.
- ) : ( - roomsData.map((room) => ( - - {room.name} - - )) - )} -
-
- )} - /> - - {/* 선택된 방의 ID를 별도의 div에 표시 */} -
- {watch("selectedRoom") && ( -
- Room ID: {watch("selectedRoom")?._id} -
- )} -
+ { + if (typeof value === "string") { + const room = roomsData.find((room) => room.name === value); + if (room) { + setSelectedMeetingRoom({ _id: room._id, name: room.name }); + } + } + }} + > + {selectedMeetingRoom?.name || "회의실 선택"} + + {roomsIsLoading ? ( +
회의실 로딩 중...
+ ) : roomsIsError ? ( +
회의실 정보를 불러오는 데 실패했습니다.
+ ) : ( + roomsData.map((room) => ( + + {room.name} + + )) + )} +
+
{/* 시작 시간 및 종료 시간 선택 */}
{/* 시작 시간 */}
( @@ -227,8 +190,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen onSelect={(value: string | boolean) => { field.onChange(value); }} - isError={Boolean(errors.startTime)} - errorMessage={errors.startTime?.message ?? ""} + isError={Boolean(errors.startAt)} + errorMessage={errors.startAt?.message ?? ""} > {field.value || selectedTime} @@ -242,20 +205,12 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen )} /> - {startTimeValue === "custom-start" && ( - } - /> - )}
{/* 종료 시간 */}
( @@ -264,8 +219,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen onSelect={(value: string | boolean) => { field.onChange(value); }} - isError={Boolean(errors.endTime)} - errorMessage={errors.endTime?.message ?? ""} + isError={Boolean(errors.endAt)} + errorMessage={errors.endAt?.message ?? ""} > {field.value || "종료 시간 선택"} @@ -279,20 +234,12 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen )} /> - {endTimeValue === "custom-end" && ( - } - /> - )}
{/* 참여자 선택 */} ( { const user = allUsersData.find((user) => user.name === name); return user ? ( -
- -
+ + {user.name} + ) : null; })} {field.value.length > 3 && ( @@ -320,7 +267,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen "참여자 선택" )} - + {allUsersIsLoading ? (
사용자 데이터 로딩 중...
) : allUsersIsError ? ( @@ -330,16 +277,23 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen ) : ( allUsersData.map((user) => ( -
- -
-
- {user.teams.map((team, index) => ( - - {team} - - ))} -
+
+ +
+ {user.teams.map((team, index) => ( +
+ {team} +
+ ))}
@@ -353,35 +307,22 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen - {/* 예약 정보 표시 */} - {selectedSchedule ? ( + {/* 제출된 데이터 표시 */} + {submittedData && (
-

예약 정보

-

- 회의 제목: {selectedSchedule.notes} -

-

- 회의실: {selectedSchedule._id} -

-

- 시작 시간: {format(new Date(selectedSchedule.startAt), "yyyy-MM-dd HH:mm")} -

-

- 종료 시간: {format(new Date(selectedSchedule.endAt), "yyyy-MM-dd HH:mm")} -

-

- 참여자: -

+

제출된 데이터

+
+            {JSON.stringify(submittedData, null, 2)}
+            
회의실: {selectedMeetingRoom!._id || "선택된 회의실 없음"}
+
- ) : null} + )}
); } From 1bff89bb64e401bdccc95da7d8ffecde4a2a8847 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 19:18:54 +0900 Subject: [PATCH 29/72] =?UTF-8?q?Feat:=20createReservationMutation=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20Form=EC=9D=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=98=88=EC=95=BD=EB=90=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index beac2942..1b0fdbd0 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -1,11 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ "use client"; import { useEffect, useState } from "react"; import { type IReservation } from "@repo/types"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { notify } from "@ui/index"; import Sidebar from "@/components/common/Sidebar"; import { type ScheduleFormData, type Schedule, type SelectedRoom } from "@/app/types/scheduletypes"; import { useSidebarStore } from "@/app/store/useSidebarStore"; +import { createReservation, type CreateReservationRequest } from "@/api/reservations"; import ReservationForm from "./ReservationForm"; import ReservationModal from "./ReservationModal"; @@ -18,12 +20,32 @@ interface DesktopReservationSheetProps { export default function DesktopReservationSheet(props: DesktopReservationSheetProps): JSX.Element { const { onClose, selectedTime, selectedSchedule, selectedRoom } = props; - + const queryClient = useQueryClient(); const { isSidebarOpen, closeSidebar } = useSidebarStore(); - const [isModalOpen, setIsModalOpen] = useState(false); - const handleSubmit = (data: ScheduleFormData): void => { + const [formData, setFormData] = useState<{ data: CreateReservationRequest; itemId: string } | null>(null); + + // Reservation mutation 설정 + const createReservationMutation = useMutation({ + mutationFn: (formData: { data: CreateReservationRequest; itemId: string }) => { + // ScheduleFormData를 CreateReservationRequest 형식으로 변환 + + return createReservation(formData.itemId, formData.data); + }, + onSuccess: async () => { + notify({ + type: "success", + message: "회의실 예약이 다다다다다.", + }); + await queryClient.invalidateQueries({ queryKey: ["reservation"] }); + setIsModalOpen(false); + onClose(); + }, + }); + + const handleSubmit = (data: CreateReservationRequest, itemId: string): void => { + setFormData({ data, itemId }); setIsModalOpen(true); }; @@ -55,6 +77,9 @@ export default function DesktopReservationSheet(props: DesktopReservationSheetPr setIsModalOpen(false); }} onConfirm={() => { + if (formData) { + createReservationMutation.mutate(formData); + } setIsModalOpen(false); onClose(); }} From 922819ec959a898a229dfa2607a1c637e1be1306 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 19:21:00 +0900 Subject: [PATCH 30/72] =?UTF-8?q?Chore:=20createReservation=20interface=20?= =?UTF-8?q?=EB=B0=8F=20data=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/api/reservations.ts | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/web/api/reservations.ts b/apps/web/api/reservations.ts index 7e4125fc..79495fb3 100644 --- a/apps/web/api/reservations.ts +++ b/apps/web/api/reservations.ts @@ -44,21 +44,37 @@ export const getReservationsByTypeAndDate = async ( }; // 예약 생성 -interface CreateReservationParams { +export interface CreateReservationParams { itemId: string; savedReservation: IReservation; +} + +export interface CreateReservationRequest { + userId: string; + itemType: "room"; + startAt: string; + endAt: string; + status: "reserved"; + notes: string; + attendees: string[]; +} + +export interface CreateReservationResponse { message: string; + savedReservation: IReservation; } -export const createReservation = async (params: CreateReservationParams): Promise => { - const { itemId, ...data } = params; - const { data: reservation } = await axiosRequester<{ message: string; savedReservation: IReservation }>({ +export const createReservation = async ( + itemId: string, + reservationData: CreateReservationRequest, +): Promise => { + const { data } = await axiosRequester({ options: { method: "POST", url: API_ENDPOINTS.RESERVATION.CREATE_RESERVATION(itemId), - data, + data: reservationData, }, }); - return reservation.savedReservation; + return data.savedReservation; }; From ccacd928d29d700704fecb729d542ff031e4b5cc Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 19:22:43 +0900 Subject: [PATCH 31/72] =?UTF-8?q?Remove:=20Modal=20notify=20remove(mutatio?= =?UTF-8?q?n=EC=97=90=20=EC=A1=B4=EC=9E=AC=ED=95=98=EB=AF=80=EB=A1=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/meetings/_components/Reservation/ReservationModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx b/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx index 865c45ee..12045a7b 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx @@ -16,7 +16,6 @@ export default function ReservationModal({ isOpen, onClose, onConfirm }: Reserva onClose={onClose} onConfirm={() => { onConfirm(); - notify({ type: "success", message: "회의실이 예약되었습니다!" }); }} title="회의실을 예약하시겠어요?" content={<>선택한 시간대의 회의실이 예약됩니다.} From 63c159a480b53195ddf482b7f3383a2b9e12a714 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 20:21:38 +0900 Subject: [PATCH 32/72] =?UTF-8?q?Fix:=2009:00=20=EC=9D=B4=ED=9B=84?= =?UTF-8?q?=EC=9D=98=20=EC=8B=9C=EA=B0=84=EB=8C=80=EB=A7=8C=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0(parseISO=EC=97=90=EC=84=9C=20Date=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Schedule/ScheduleRow/index.tsx | 17 ++++++++--------- .../ScheduleTable/ScheduleTableDesktop.tsx | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index f666dac2..ebd48e72 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -6,12 +6,12 @@ import { type IReservation } from "@repo/types"; import { parseISO } from "date-fns"; import { useSidebarStore } from "@/app/store/useSidebarStore"; import { useAuthStore } from "@/src/stores/useAuthStore"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; import MobileReservationSheet from "../../Reservation/MobileReservationSheet"; import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; import ScheduleSlot from "./ScheduleSlot"; import ScheduleItem from "./ScheduleItem"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; -import { SelectedRoom } from "@/app/types/scheduletypes"; interface ScheduleRowProps { schedules: IReservation[]; @@ -40,14 +40,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const totalMinutes = (endHour - startHour) * 60; const timeToMinutes = (time: Date | string): number => { - let date: Date; - - if (typeof time === "string") { - date = parseISO(time); - } else { - date = time; - } - + const date = typeof time === "string" ? new Date(time) : time; const hours = date.getHours(); const minutes = date.getMinutes(); return hours * 60 + minutes; @@ -97,6 +90,12 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; const scheduleDuration = endMinutes - startMinutes; + console.log(`Schedule ID: ${schedule._id}`); + console.log(`Start Minutes: ${startMinutes}`); + console.log(`End Minutes: ${endMinutes}`); + console.log(`Schedule Duration: ${schedule.notes}`); + console.log(`Schedule Width: ${(scheduleDuration / totalMinutes) * (slotWidth * totalSlots)}`); + if (startMinutes < 0 || endMinutes > totalMinutes) return null; const leftPosition = (startMinutes / totalMinutes) * (slotWidth * totalSlots); diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index 130354a5..98703077 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -33,7 +33,7 @@ export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps): const isSameRoom = scheduleItemId === room._id; - const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; + const scheduleDate = new Date(schedule.startAt).toLocaleDateString("en-CA", { timeZone: "Asia/Seoul" }); const isSameDate = scheduleDate === selectedDate; return isSameRoom && isSameDate; From 04bf14687fc97f46d4e4fd690bb6c7250e01e2d6 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 18 Nov 2024 20:42:45 +0900 Subject: [PATCH 33/72] =?UTF-8?q?Feat:=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=97=90=EC=84=9C=2030=EB=B6=84=20=EB=92=A4=EC=9D=98?= =?UTF-8?q?=20=EC=A2=85=EB=A3=8C=EC=8B=9C=EA=B0=84=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95,=20error=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 89 ++++++++++++++++--- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 1ae2ac07..d49fc069 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -7,7 +7,7 @@ import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; import { useEffect, useState } from "react"; import { type TBaseItem, type IReservation, type IUser } from "@repo/types"; -import { format } from "date-fns"; +import { format, parse, differenceInMinutes, addMinutes } from "date-fns"; // Import addMinutes import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Badge, notify } from "@ui/index"; import { timeOptions } from "@/app/constants/timeOptions"; @@ -16,7 +16,7 @@ import { type SelectedRoom, type ScheduleFormData } from "@/app/types/schedulety import { getAllUser } from "@/api/users"; import { getAllItems } from "@/api/items"; import { useAuthStore } from "@/src/stores/useAuthStore"; -import { createReservation, CreateReservationRequest } from "@/api/reservations"; +import { createReservation, type CreateReservationRequest } from "@/api/reservations"; import { useDateStore } from "@/app/store/useDateStore"; interface ReservationFormProps { @@ -57,13 +57,20 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen queryFn: getAllUser, }); + // 시작 시간을 기반으로 종료 시간을 설정하는 함수 + const getDefaultEndAt = (startAt: string): string => { + const start = parse(startAt, "HH:mm", new Date()); + const end = addMinutes(start, 30); + return format(end, "HH:mm"); + }; + // 폼의 기본 값을 설정합니다. const defaultValues: CreateReservationRequest = { userId: user!._id, itemType: "room", notes: selectedSchedule?.notes ?? "", startAt: selectedSchedule ? format(new Date(selectedSchedule.startAt), "HH:mm") : selectedTime, - endAt: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : "", + endAt: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : getDefaultEndAt(selectedTime), // 기본 종료 시간을 설정 status: "reserved", attendees: [], }; @@ -79,6 +86,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen clearErrors, } = useForm({ defaultValues, + mode: "onChange", // Enable validation on change }); // 상태를 추가하여 제출된 데이터를 저장 @@ -96,21 +104,72 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const [selectedMeetingRoom, setSelectedMeetingRoom] = useState(selectedRoom); const attendeessSelected = watch("attendees").length > 0; + const startAtValue = watch("startAt"); + const endAtValue = watch("endAt"); // resetTrigger 또는 selectedSchedule이 변경될 때마다 폼을 리셋 useEffect(() => { - reset(defaultValues); + const newEndAt = selectedSchedule + ? format(new Date(selectedSchedule.endAt), "HH:mm") + : getDefaultEndAt(selectedTime); + reset({ + ...defaultValues, + endAt: newEndAt, + }); }, [resetTrigger, reset, selectedTime, selectedRoom, selectedSchedule]); - // startTime 또는 customStartTime이 변경될 때 endTime을 설정 + // startAt 값이 변경될 때 endAt을 자동으로 30분 후로 설정 + useEffect(() => { + if (startAtValue) { + const currentEndAt = parse(endAtValue, "HH:mm", new Date()); + const calculatedEndAt = addMinutes(parse(startAtValue, "HH:mm", new Date()), 30); - // 시작 시간과 종료 시간을 비교하여 유효성 검사 + // 종료 시간이 현재 설정된 종료 시간보다 작을 경우, 종료 시간을 30분 후로 설정 + if (differenceInMinutes(calculatedEndAt, currentEndAt) > 0) { + const newEndAt = format(calculatedEndAt, "HH:mm"); + setValue("endAt", newEndAt); + } + } + }, [startAtValue, endAtValue, setValue]); + + // Custom validation to ensure endAt is at least 30 minutes after startAt + const validateEndAt = (endAt: string): boolean | string => { + if (!startAtValue || !endAt) { + return "시작 시간과 종료 시간을 모두 선택해주세요."; + } + + // Parse the startAt and endAt times + const start = parse(startAtValue, "HH:mm", new Date()); + const end = parse(endAt, "HH:mm", new Date()); + + // Calculate the difference in minutes + const diff = differenceInMinutes(end, start); + + if (diff < 30) { + return "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다."; + } + + return true; + }; const onFormSubmit = (data: CreateReservationRequest): void => { if (!user || !selectedRoom?._id) { return; } + // Additional validation to ensure endAt is at least 30 minutes after startAt + const start = parse(data.startAt, "HH:mm", new Date()); + const end = parse(data.endAt, "HH:mm", new Date()); + const diff = differenceInMinutes(end, start); + + if (diff < 30) { + setError("endAt", { + type: "manual", + message: "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다.", + }); + return; + } + const attendeeIds = data.attendees .map((name: string) => { const selectedUser = allUsersData.find((user) => user.name === name); @@ -189,6 +248,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen selectedValue={field.value} onSelect={(value: string | boolean) => { field.onChange(value); + // Clear endAt error when startAt changes + clearErrors("endAt"); }} isError={Boolean(errors.startAt)} errorMessage={errors.startAt?.message ?? ""} @@ -212,7 +273,10 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen ( 예약하기 {/* 제출된 데이터 표시 */} - {submittedData && ( + {submittedData ? (

제출된 데이터

             {JSON.stringify(submittedData, null, 2)}
-            
회의실: {selectedMeetingRoom!._id || "선택된 회의실 없음"}
+
+ 회의실: {selectedMeetingRoom?._id || "선택된 회의실 없음"} + {selectedTime} +
- )} + ) : null}
); } From 50ecef94b228b6b25880dd8b08ec299d7e3355e5 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Tue, 19 Nov 2024 16:15:24 +0900 Subject: [PATCH 34/72] =?UTF-8?q?Feat:=20ReservationForm=20=EC=A0=9C?= =?UTF-8?q?=EC=B6=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20state=20type=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 5 +- .../Reservation/ReservationForm.tsx | 71 ++++++++++--------- .../Schedule/ScheduleRow/index.tsx | 24 ++----- 3 files changed, 47 insertions(+), 53 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index 1b0fdbd0..f200e0d0 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -26,17 +26,14 @@ export default function DesktopReservationSheet(props: DesktopReservationSheetPr const [formData, setFormData] = useState<{ data: CreateReservationRequest; itemId: string } | null>(null); - // Reservation mutation 설정 const createReservationMutation = useMutation({ mutationFn: (formData: { data: CreateReservationRequest; itemId: string }) => { - // ScheduleFormData를 CreateReservationRequest 형식으로 변환 - return createReservation(formData.itemId, formData.data); }, onSuccess: async () => { notify({ type: "success", - message: "회의실 예약이 다다다다다.", + message: "회의실이 예약되었습니다..", }); await queryClient.invalidateQueries({ queryKey: ["reservation"] }); setIsModalOpen(false); diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index d49fc069..734a57b2 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -1,3 +1,5 @@ +// ReservationForm.tsx + "use client"; import Input from "@ui/src/components/common/Input"; @@ -7,20 +9,20 @@ import Button from "@ui/src/components/common/Button"; import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; import { useEffect, useState } from "react"; import { type TBaseItem, type IReservation, type IUser } from "@repo/types"; -import { format, parse, differenceInMinutes, addMinutes } from "date-fns"; // Import addMinutes -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { Badge, notify } from "@ui/index"; +import { format, parse, differenceInMinutes, addMinutes } from "date-fns"; +import { useQuery } from "@tanstack/react-query"; +import { Badge } from "@ui/index"; import { timeOptions } from "@/app/constants/timeOptions"; import Profile from "@/components/common/Profile"; -import { type SelectedRoom, type ScheduleFormData } from "@/app/types/scheduletypes"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; import { getAllUser } from "@/api/users"; import { getAllItems } from "@/api/items"; import { useAuthStore } from "@/src/stores/useAuthStore"; -import { createReservation, type CreateReservationRequest } from "@/api/reservations"; +import { type CreateReservationRequest } from "@/api/reservations"; import { useDateStore } from "@/app/store/useDateStore"; interface ReservationFormProps { - onSubmit: (data: CreateReservationRequest, itemId: string) => void; // itemId 파라미터 추가 + onSubmit: (data: CreateReservationRequest, itemId: string) => void; selectedTime: string; selectedSchedule?: IReservation | null; resetTrigger?: number; @@ -32,12 +34,12 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const MeetingRoomsType = "room"; - // useAuthStore를 사용하여 현재 사용자 데이터 가져오기 - const user = useAuthStore((state) => state.user); // 현재 사용자의 데이터 가져오기 + // 현재 사용자 데이터 가져오기 + const user = useAuthStore((state) => state.user); const { selectedDate } = useDateStore(); - // 방 데이터를 useQuery로 가져오기 + // 방 데이터 가져오기 const { data: roomsData = [], isLoading: roomsIsLoading, @@ -47,7 +49,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen queryFn: () => getAllItems({ itemType: MeetingRoomsType }), }); - // 전체 사용자 데이터를 useQuery로 가져오기 + // 전체 사용자 데이터 가져오기 const { data: allUsersData = [], isLoading: allUsersIsLoading, @@ -70,7 +72,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen itemType: "room", notes: selectedSchedule?.notes ?? "", startAt: selectedSchedule ? format(new Date(selectedSchedule.startAt), "HH:mm") : selectedTime, - endAt: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : getDefaultEndAt(selectedTime), // 기본 종료 시간을 설정 + endAt: selectedSchedule ? format(new Date(selectedSchedule.endAt), "HH:mm") : getDefaultEndAt(selectedTime), status: "reserved", attendees: [], }; @@ -86,37 +88,40 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen clearErrors, } = useForm({ defaultValues, - mode: "onChange", // Enable validation on change + mode: "onChange", }); // 상태를 추가하여 제출된 데이터를 저장 - const [submittedData, setSubmittedData] = useState<{ - userId: string; - itemType: string; - startAt: string; - endAt: string; - status: string; - notes: string; - attendees: string[]; // 사용자 ID 배열로 변경 - } | null>(null); + const [submittedData, setSubmittedData] = useState(null); // ReservationForm 컴포넌트 내 const [selectedMeetingRoom, setSelectedMeetingRoom] = useState(selectedRoom); - const attendeessSelected = watch("attendees").length > 0; + const attendeesSelected = watch("attendees").length > 0; const startAtValue = watch("startAt"); const endAtValue = watch("endAt"); // resetTrigger 또는 selectedSchedule이 변경될 때마다 폼을 리셋 useEffect(() => { - const newEndAt = selectedSchedule - ? format(new Date(selectedSchedule.endAt), "HH:mm") - : getDefaultEndAt(selectedTime); - reset({ - ...defaultValues, - endAt: newEndAt, - }); - }, [resetTrigger, reset, selectedTime, selectedRoom, selectedSchedule]); + if (allUsersData.length > 0) { + const newEndAt = selectedSchedule + ? format(new Date(selectedSchedule.endAt), "HH:mm") + : getDefaultEndAt(selectedTime); + const attendeeNames = selectedSchedule?.attendees + ? selectedSchedule.attendees + .map((attendeeId) => { + const user = allUsersData.find((user) => user === attendeeId); + return user ? user.name : null; + }) + .filter((name): name is string => name !== null) + : []; + reset({ + ...defaultValues, + endAt: newEndAt, + attendees: attendeeNames, + }); + } + }, [resetTrigger, reset, selectedTime, selectedRoom, selectedSchedule, allUsersData]); // startAt 값이 변경될 때 endAt을 자동으로 30분 후로 설정 useEffect(() => { @@ -207,6 +212,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen rules={{ required: "미팅 제목을 입력해주세요." }} render={({ field }) => } /> + {/* 미팅룸 선택 */} ( + {/* 예약하기 버튼 */} diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index ebd48e72..b3a69653 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -1,5 +1,4 @@ -/* eslint-disable react/no-array-index-key */ -"use client"; +// ScheduleRow.tsx import { useState } from "react"; import { type IReservation } from "@repo/types"; @@ -20,6 +19,7 @@ interface ScheduleRowProps { slotHeight?: number; onSlotClick?: (time: string, schedule?: IReservation, room?: { name: string; _id: string }) => void; } + export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const { schedules, room, slotHeight = 80, slotWidth = 72 } = props; @@ -35,12 +35,12 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const startHour = 0; const endHour = 24; - const totalSlots = (endHour - startHour) * 2; + const totalSlots = (endHour - startHour) * 2; // 30분 단위 const minutesPerSlot = 30; const totalMinutes = (endHour - startHour) * 60; const timeToMinutes = (time: Date | string): number => { - const date = typeof time === "string" ? new Date(time) : time; + const date = typeof time === "string" ? parseISO(time) : time; const hours = date.getHours(); const minutes = date.getMinutes(); return hours * 60 + minutes; @@ -70,13 +70,11 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element {
{Array.from({ length: totalSlots }).map((_, index) => ( { - handleSlotClick(index); - }} + onClick={() => handleSlotClick(index)} /> ))}
@@ -90,12 +88,6 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; const scheduleDuration = endMinutes - startMinutes; - console.log(`Schedule ID: ${schedule._id}`); - console.log(`Start Minutes: ${startMinutes}`); - console.log(`End Minutes: ${endMinutes}`); - console.log(`Schedule Duration: ${schedule.notes}`); - console.log(`Schedule Width: ${(scheduleDuration / totalMinutes) * (slotWidth * totalSlots)}`); - if (startMinutes < 0 || endMinutes > totalMinutes) return null; const leftPosition = (startMinutes / totalMinutes) * (slotWidth * totalSlots); @@ -108,9 +100,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { leftPosition={leftPosition} scheduleWidth={scheduleWidth} isCurrentUser={schedule.user._id === user?._id} - onClick={() => { - handleSlotClick(-1, schedule); - }} + onClick={() => handleSlotClick(-1, schedule)} /> ); })} From eae0d8dedffd2b899015e65a4a2fc6365bee83ba Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Tue, 19 Nov 2024 16:44:46 +0900 Subject: [PATCH 35/72] =?UTF-8?q?Feat:=20=EC=98=88=EC=95=BD=20=EC=8B=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=20=EC=98=88=EC=95=BD=EB=90=9C=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=EC=9D=84=20=EC=B9=A8=EB=B2=94=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20error=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 192 ++++++++++++++---- 1 file changed, 155 insertions(+), 37 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 734a57b2..bd09256f 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -1,5 +1,3 @@ -// ReservationForm.tsx - "use client"; import Input from "@ui/src/components/common/Input"; @@ -18,7 +16,7 @@ import { type SelectedRoom } from "@/app/types/scheduletypes"; import { getAllUser } from "@/api/users"; import { getAllItems } from "@/api/items"; import { useAuthStore } from "@/src/stores/useAuthStore"; -import { type CreateReservationRequest } from "@/api/reservations"; +import { getReservationsByTypeAndDate, type CreateReservationRequest } from "@/api/reservations"; import { useDateStore } from "@/app/store/useDateStore"; interface ReservationFormProps { @@ -39,6 +37,16 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const { selectedDate } = useDateStore(); + const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart( + 2, + "0", + )}-${String(selectedDate.day).padStart(2, "0")}`; + + const { data: meetingsData = [], isLoading: meetingsIsLoading } = useQuery({ + queryKey: ["meetings", formattedDate, MeetingRoomsType], + queryFn: () => getReservationsByTypeAndDate({ itemType: MeetingRoomsType, date: formattedDate }), + }); + // 방 데이터 가져오기 const { data: roomsData = [], @@ -83,6 +91,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen watch, formState: { errors, isValid }, reset, + getValues, + trigger, setError, setValue, clearErrors, @@ -91,6 +101,77 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen mode: "onChange", }); + const validateEndAt = (endAt: string): boolean | string => { + const startAtValue = getValues("startAt"); + + if (!startAtValue || !endAt) { + return "시작 시간과 종료 시간을 모두 선택해주세요."; + } + + // 시작 시간과 종료 시간을 파싱합니다. + const start = parse(startAtValue, "HH:mm", new Date()); + const end = parse(endAt, "HH:mm", new Date()); + + // 시간 차이를 계산합니다. + const diff = differenceInMinutes(end, start); + + if (diff < 30) { + return "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다."; + } + + // 추가: 시간 겹침 여부 확인 + if (!selectedMeetingRoom?._id) { + return true; // 회의실이 선택되지 않은 경우 검증 통과 + } + + const { year, month, day } = selectedDate; + + const newStart = new Date( + `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${startAtValue}:00`, + ); + const newEnd = new Date(`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${endAt}:00`); + + const isOverlap = meetingsData.some((reservation) => { + // 예약의 itemType이 "room"인지 확인합니다. + if (reservation.itemType !== "room") { + return false; + } + + // 예약의 item에서 itemId를 추출합니다. + let itemId: string; + if (typeof reservation.item === "string") { + itemId = reservation.item; + } else if (reservation.item && "_id" in reservation.item) { + itemId = reservation.item._id; + } else { + return false; // itemId를 추출할 수 없으면 건너뜁니다. + } + + // 현재 선택된 회의실과 동일한지 확인합니다. + if (itemId !== selectedMeetingRoom._id) { + return false; + } + + // 현재 수정 중인 예약은 제외합니다. + if (selectedSchedule && reservation._id === selectedSchedule._id) { + return false; + } + + // 기존 예약의 시작 시간과 종료 시간을 Date 객체로 변환합니다. + const existingStart = new Date(reservation.startAt); + const existingEnd = new Date(reservation.endAt); + + // 시간 겹침 여부를 확인합니다. + return newStart < existingEnd && newEnd > existingStart; + }); + + if (isOverlap) { + return "선택한 시간에 이미 예약이 있습니다."; + } + + return true; + }; + // 상태를 추가하여 제출된 데이터를 저장 const [submittedData, setSubmittedData] = useState(null); @@ -109,11 +190,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen : getDefaultEndAt(selectedTime); const attendeeNames = selectedSchedule?.attendees ? selectedSchedule.attendees - .map((attendeeId) => { - const user = allUsersData.find((user) => user === attendeeId); - return user ? user.name : null; - }) - .filter((name): name is string => name !== null) + .map((attendee) => attendee.name) + .filter((name): name is string => name !== undefined) : []; reset({ ...defaultValues, @@ -137,44 +215,84 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen } }, [startAtValue, endAtValue, setValue]); - // Custom validation to ensure endAt is at least 30 minutes after startAt - const validateEndAt = (endAt: string): boolean | string => { - if (!startAtValue || !endAt) { - return "시작 시간과 종료 시간을 모두 선택해주세요."; + // onFormSubmit 함수 수정 + const onFormSubmit = (data: CreateReservationRequest): void => { + if (!user || !selectedMeetingRoom?._id) { + return; } - // Parse the startAt and endAt times - const start = parse(startAtValue, "HH:mm", new Date()); - const end = parse(endAt, "HH:mm", new Date()); + const { year, month, day } = selectedDate; - // Calculate the difference in minutes - const diff = differenceInMinutes(end, start); + // 새로운 예약의 시작 시간과 종료 시간을 Date 객체로 변환합니다. + const newStart = new Date( + `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.startAt}:00`, + ); + const newEnd = new Date( + `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.endAt}:00`, + ); - if (diff < 30) { - return "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다."; + // 추가적인 검증: 종료 시간이 시작 시간 이후인지 확인합니다. + if (newEnd <= newStart) { + setError("endAt", { + type: "manual", + message: "종료 시간은 시작 시간 이후여야 합니다.", + }); + return; } - return true; - }; - - const onFormSubmit = (data: CreateReservationRequest): void => { - if (!user || !selectedRoom?._id) { - return; + // 시간 겹침 여부를 확인하는 함수 + function isTimeOverlap(newStart: Date, newEnd: Date, existingStart: Date, existingEnd: Date): boolean { + return newStart < existingEnd && newEnd > existingStart; } - // Additional validation to ensure endAt is at least 30 minutes after startAt - const start = parse(data.startAt, "HH:mm", new Date()); - const end = parse(data.endAt, "HH:mm", new Date()); - const diff = differenceInMinutes(end, start); + // 기존 예약과 시간 겹침 여부를 확인합니다. + const isOverlap = meetingsData.some((reservation) => { + // 예약의 itemType이 "room"인지 확인합니다. + if (reservation.itemType !== "room") { + return false; + } - if (diff < 30) { + // 예약의 item에서 itemId를 추출합니다. + let itemId: string; + if (typeof reservation.item === "string") { + itemId = reservation.item; + } else if (reservation.item && "_id" in reservation.item) { + itemId = reservation.item._id; + } else { + return false; // itemId를 추출할 수 없으면 건너뜁니다. + } + + // 현재 선택된 회의실과 동일한지 확인합니다. + if (itemId !== selectedMeetingRoom._id) { + return false; + } + + // 현재 수정 중인 예약은 제외합니다. + if (selectedSchedule && reservation._id === selectedSchedule._id) { + return false; + } + + // 기존 예약의 시작 시간과 종료 시간을 Date 객체로 변환합니다. + const existingStart = new Date(reservation.startAt); + const existingEnd = new Date(reservation.endAt); + + // 시간 겹침 여부를 확인합니다. + return isTimeOverlap(newStart, newEnd, existingStart, existingEnd); + }); + + if (isOverlap) { + setError("startAt", { + type: "manual", + message: "선택한 시간에 이미 예약이 있습니다.", + }); setError("endAt", { type: "manual", - message: "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다.", + message: "선택한 시간에 이미 예약이 있습니다.", }); return; } + // 참석자 ID 배열 생성 const attendeeIds = data.attendees .map((name: string) => { const selectedUser = allUsersData.find((user) => user.name === name); @@ -182,10 +300,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen }) .filter((id): id is string => id !== null); - const { year, month, day } = selectedDate; - - const startAt = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.startAt}:00`; - const endAt = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.endAt}:00`; + const startAt = newStart.toISOString(); + const endAt = newEnd.toISOString(); const mappedData: CreateReservationRequest = { userId: user._id, @@ -221,6 +337,8 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen const room = roomsData.find((room) => room.name === value); if (room) { setSelectedMeetingRoom({ _id: room._id, name: room.name }); + // Trigger validation of endAt when meeting room changes + trigger("endAt"); } } }} @@ -256,13 +374,14 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen field.onChange(value); // Clear endAt error when startAt changes clearErrors("endAt"); + // Trigger validation of endAt + trigger("endAt"); }} isError={Boolean(errors.startAt)} errorMessage={errors.startAt?.message ?? ""} > {field.value || selectedTime} - 직접입력 {timeOptions.map((time) => ( {time} @@ -294,7 +413,6 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen > {field.value || "종료 시간 선택"} - 직접입력 {timeOptions.map((time) => ( {time} From 8c0ea9ac3628e2e12656c5649df5d984202b19c7 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Tue, 19 Nov 2024 18:11:47 +0900 Subject: [PATCH 36/72] =?UTF-8?q?Feat:=20ScheduleItem=20=EB=8C=80=EC=8B=A0?= =?UTF-8?q?=20Slot=EC=97=90=EC=84=9C=20=EC=98=88=EC=95=BD=EB=90=9C=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=8F=84=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Schedule/ScheduleRow/ScheduleSlot.tsx | 40 +++++--- .../Schedule/ScheduleRow/index.tsx | 96 ++++++++++++------- 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx index bedf6d1b..5ca2ba87 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx @@ -1,26 +1,31 @@ -/* eslint-disable jsx-a11y/click-events-have-key-events */ +// ScheduleSlot.tsx -"use client"; import { useEffect, useState } from "react"; import { useSidebarStore } from "@/app/store/useSidebarStore"; +import { useAuthStore } from "@/src/stores/useAuthStore"; // 현재 사용자 정보를 가져오기 위해 추가 +import { type IReservation } from "@repo/types"; interface ScheduleSlotProps { index: number; slotWidth: number; slotHeight: number; - onClick: (index: number) => void; + slotCount: number; + onClick: () => void; + isReserved: boolean; + schedule?: IReservation; } export default function ScheduleSlot(props: ScheduleSlotProps): JSX.Element { - const { index, slotHeight, slotWidth, onClick } = props; + const { index, slotHeight, slotWidth, slotCount, onClick, isReserved, schedule } = props; - const { isSidebarOpen, openSidebar } = useSidebarStore(); + const { isSidebarOpen } = useSidebarStore(); const [isClicked, setIsClicked] = useState(false); + const user = useAuthStore((state) => state.user); // 현재 사용자 정보 가져오기 + const handleClick = (): void => { setIsClicked(true); - openSidebar(); - onClick(index); + onClick(); }; // Sidebar가 닫힐 때 클릭 상태 초기화 @@ -30,21 +35,34 @@ export default function ScheduleSlot(props: ScheduleSlotProps): JSX.Element { } }, [isSidebarOpen]); + // 예약된 슬롯이 현재 사용자의 예약인지 확인 + const isUserReservation = isReserved && schedule && schedule.user._id === user?._id; + return (
- {isClicked ?
: null} -
+ {/* 예약된 슬롯의 경우 중간에 가로 막대를 표시 */} + {isReserved && schedule ? ( +
+
+
+ ) : null} + + {/* 좌측 테두리 */} {index % 2 === 0 ? (
) : (
)} + + {/* 하단 테두리 */}
); diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index b3a69653..f3b3da7b 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -9,7 +9,6 @@ import { type SelectedRoom } from "@/app/types/scheduletypes"; import MobileReservationSheet from "../../Reservation/MobileReservationSheet"; import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; import ScheduleSlot from "./ScheduleSlot"; -import ScheduleItem from "./ScheduleItem"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; interface ScheduleRowProps { @@ -39,6 +38,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const minutesPerSlot = 30; const totalMinutes = (endHour - startHour) * 60; + // timeToMinutes 함수 선언 const timeToMinutes = (time: Date | string): number => { const date = typeof time === "string" ? parseISO(time) : time; const hours = date.getHours(); @@ -46,6 +46,66 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { return hours * 60 + minutes; }; + // 슬롯별로 예약 상태를 저장하는 배열 생성 + const slotReservations = Array(totalSlots).fill(null) as Array; + + // 예약 정보를 슬롯 인덱스에 매핑 + schedules.forEach((schedule) => { + const startMinutes = timeToMinutes(schedule.startAt) - startHour * 60; + const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; + + const startIndex = Math.floor(startMinutes / minutesPerSlot); + const endIndex = Math.ceil(endMinutes / minutesPerSlot); + + for (let i = startIndex; i < endIndex; i++) { + slotReservations[i] = schedule; + } + }); + + // 슬롯 생성 + const slots = []; + for (let i = 0; i < totalSlots; i++) { + const schedule = slotReservations[i]; + if (schedule) { + // 예약된 슬롯의 시작 위치를 찾기 위해 이전 슬롯을 확인 + if (i === 0 || slotReservations[i - 1] !== schedule) { + // 예약된 슬롯의 시작 + const start = i; + let end = i; + // 예약된 슬롯의 끝 위치를 찾음 + while (end < totalSlots && slotReservations[end] === schedule) { + end++; + } + slots.push( + handleSlotClick(start, schedule)} + isReserved={true} + schedule={schedule} + />, + ); + i = end - 1; // 다음 반복에서 end 위치부터 시작 + } + } else { + // 예약되지 않은 슬롯은 개별적으로 렌더링 + slots.push( + handleSlotClick(i)} + isReserved={false} + />, + ); + } + } + const handleSlotClick = (index: number, schedule?: IReservation): void => { const clickedTimeMinutes = startHour * 60 + index * minutesPerSlot; const hours = Math.floor(clickedTimeMinutes / 60); @@ -67,44 +127,12 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { return (
-
- {Array.from({ length: totalSlots }).map((_, index) => ( - handleSlotClick(index)} - /> - ))} -
+
{slots}
- {schedules.map((schedule) => { - const startMinutes = timeToMinutes(schedule.startAt) - startHour * 60; - const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; - const scheduleDuration = endMinutes - startMinutes; - - if (startMinutes < 0 || endMinutes > totalMinutes) return null; - - const leftPosition = (startMinutes / totalMinutes) * (slotWidth * totalSlots); - const scheduleWidth = (scheduleDuration / totalMinutes) * (slotWidth * totalSlots); - - return ( - handleSlotClick(-1, schedule)} - /> - ); - })} - {selectedTime && selectedRoom ? ( <>
From d43dfec1caec6d0f6c8757e228192967ddf14fe9 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Tue, 19 Nov 2024 18:18:26 +0900 Subject: [PATCH 37/72] =?UTF-8?q?Feat:=20=EC=98=88=EC=95=BD=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=88=98=EC=A0=95=ED=95=A0updateReservati?= =?UTF-8?q?on=20=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/api/reservations.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/web/api/reservations.ts b/apps/web/api/reservations.ts index 79495fb3..a28ed62a 100644 --- a/apps/web/api/reservations.ts +++ b/apps/web/api/reservations.ts @@ -78,3 +78,31 @@ export const createReservation = async ( return data.savedReservation; }; + +export interface UpdateReservationRequest { + startAt?: string; + endAt?: string; + status?: TReservationStatus; + notes?: string; + attendees?: string[]; +} + +export interface UpdateReservationResponse { + message: string; + updatedReservation: IReservation; +} + +export const updateReservation = async ( + reservationId: string, + reservationData: UpdateReservationRequest, +): Promise => { + const { data } = await axiosRequester({ + options: { + method: "PATCH", + url: API_ENDPOINTS.RESERVATION.UPDATE_RESERVATION(reservationId), + data: reservationData, + }, + }); + + return data.updatedReservation; +}; From c5e6928156212ca4cca2ff73a2a4a1e439ee6b0f Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Tue, 19 Nov 2024 18:27:12 +0900 Subject: [PATCH 38/72] =?UTF-8?q?Feat:=20=EC=98=88=EC=95=BD=EB=90=9C=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EC=9D=84=20=ED=81=B4=EB=A6=AD?= =?UTF-8?q?=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20=EB=B2=84=ED=8A=BC=EC=9D=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=EB=A1=9C=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 37 ++++++++++++++----- .../Reservation/ReservationForm.tsx | 3 +- .../Schedule/ScheduleRow/index.tsx | 13 ++++--- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index f200e0d0..214fa4bd 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -7,9 +7,10 @@ import { notify } from "@ui/index"; import Sidebar from "@/components/common/Sidebar"; import { type ScheduleFormData, type Schedule, type SelectedRoom } from "@/app/types/scheduletypes"; import { useSidebarStore } from "@/app/store/useSidebarStore"; -import { createReservation, type CreateReservationRequest } from "@/api/reservations"; +import { createReservation, updateReservation, type CreateReservationRequest } from "@/api/reservations"; import ReservationForm from "./ReservationForm"; import ReservationModal from "./ReservationModal"; +import { useAuthStore } from "@/src/stores/useAuthStore"; interface DesktopReservationSheetProps { onClose: () => void; @@ -24,16 +25,30 @@ export default function DesktopReservationSheet(props: DesktopReservationSheetPr const { isSidebarOpen, closeSidebar } = useSidebarStore(); const [isModalOpen, setIsModalOpen] = useState(false); - const [formData, setFormData] = useState<{ data: CreateReservationRequest; itemId: string } | null>(null); + const user = useAuthStore((state) => state.user); - const createReservationMutation = useMutation({ - mutationFn: (formData: { data: CreateReservationRequest; itemId: string }) => { - return createReservation(formData.itemId, formData.data); + const [formData, setFormData] = useState<{ + data: CreateReservationRequest; + itemId: string; + reservationId?: string; + } | null>(null); + + const isEditMode = !!selectedSchedule && selectedSchedule.user._id === user?._id; + + const createOrUpdateReservation = useMutation({ + mutationFn: (formData: { data: CreateReservationRequest; itemId: string; reservationId?: string }) => { + if (isEditMode && formData.reservationId) { + // 수정 모드일 경우 + return updateReservation(formData.reservationId, formData.data); + } else { + // 생성 모드일 경우 + return createReservation(formData.itemId, formData.data); + } }, onSuccess: async () => { notify({ type: "success", - message: "회의실이 예약되었습니다..", + message: isEditMode ? "예약이 수정되었습니다." : "회의실이 예약되었습니다.", }); await queryClient.invalidateQueries({ queryKey: ["reservation"] }); setIsModalOpen(false); @@ -41,8 +56,12 @@ export default function DesktopReservationSheet(props: DesktopReservationSheetPr }, }); - const handleSubmit = (data: CreateReservationRequest, itemId: string): void => { - setFormData({ data, itemId }); + const handleSubmit = ( + data: CreateReservationRequest, + itemId: string, + reservationId?: string, // 수정 모드일 경우 reservationId 추가 + ): void => { + setFormData({ data, itemId, reservationId }); setIsModalOpen(true); }; @@ -75,7 +94,7 @@ export default function DesktopReservationSheet(props: DesktopReservationSheetPr }} onConfirm={() => { if (formData) { - createReservationMutation.mutate(formData); + createOrUpdateReservation.mutate(formData); } setIsModalOpen(false); onClose(); diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index bd09256f..27e24979 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -34,6 +34,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen // 현재 사용자 데이터 가져오기 const user = useAuthStore((state) => state.user); + const isEditMode = !!selectedSchedule && selectedSchedule.user._id === user?._id; const { selectedDate } = useDateStore(); @@ -499,7 +500,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen onClick={handleSubmit(onFormSubmit)} isActive={isValid && attendeesSelected} > - 예약하기 + {isEditMode ? "수정하기" : "예약하기"} {/* 제출된 데이터 표시 */} diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index f3b3da7b..d591cf76 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -47,7 +47,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { }; // 슬롯별로 예약 상태를 저장하는 배열 생성 - const slotReservations = Array(totalSlots).fill(null) as Array; + const slotReservations = Array(totalSlots).fill(null) as (IReservation | null)[]; // 예약 정보를 슬롯 인덱스에 매핑 schedules.forEach((schedule) => { @@ -83,8 +83,10 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { slotCount={end - start} slotWidth={slotWidth} slotHeight={slotHeight} - onClick={() => handleSlotClick(start, schedule)} - isReserved={true} + onClick={() => { + handleSlotClick(start, schedule); + }} + isReserved schedule={schedule} />, ); @@ -99,7 +101,9 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { slotCount={1} slotWidth={slotWidth} slotHeight={slotHeight} - onClick={() => handleSlotClick(i)} + onClick={() => { + handleSlotClick(i); + }} isReserved={false} />, ); @@ -132,7 +136,6 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element {
- {selectedTime && selectedRoom ? ( <>
From 5e530ca086eefdb6d403dec04b3a3ec3eb821c62 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Tue, 19 Nov 2024 18:54:25 +0900 Subject: [PATCH 39/72] =?UTF-8?q?Fix:=20reservationId=20=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20error=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/meetings/_components/Reservation/ReservationForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 27e24979..bcc5b729 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -20,7 +20,7 @@ import { getReservationsByTypeAndDate, type CreateReservationRequest } from "@/a import { useDateStore } from "@/app/store/useDateStore"; interface ReservationFormProps { - onSubmit: (data: CreateReservationRequest, itemId: string) => void; + onSubmit: (data: CreateReservationRequest, itemId: string, reservationId?: string) => void; selectedTime: string; selectedSchedule?: IReservation | null; resetTrigger?: number; @@ -317,7 +317,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen // 상태에 저장하여 화면에 표시 setSubmittedData(mappedData); - onSubmit(mappedData, selectedMeetingRoom!._id); + onSubmit(mappedData, selectedMeetingRoom!._id, selectedSchedule?._id); }; return ( From 63ef4838650b70ea084af5c1d70d403aadb2e5fa Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 15:10:07 +0900 Subject: [PATCH 40/72] =?UTF-8?q?Feat:=20deleteReservation=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/api/reservations.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/web/api/reservations.ts b/apps/web/api/reservations.ts index a28ed62a..49a59902 100644 --- a/apps/web/api/reservations.ts +++ b/apps/web/api/reservations.ts @@ -106,3 +106,12 @@ export const updateReservation = async ( return data.updatedReservation; }; + +export const deleteReservation = async (reservationId: string): Promise => { + await axiosRequester({ + options: { + method: "DELETE", + url: API_ENDPOINTS.RESERVATION.DELETE_RESERVATION(reservationId), + }, + }); +}; From d40cc8d9090036b68db7279a499aa37680b902aa Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 17:32:36 +0900 Subject: [PATCH 41/72] =?UTF-8?q?Feat:=20ReservationModal=20title,=20conte?= =?UTF-8?q?nt=20=EB=B0=9B=EC=9D=84=20=EC=88=98=20=EC=9E=88=EA=B2=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationModal.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx b/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx index 12045a7b..d625035c 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx @@ -1,15 +1,27 @@ "use client"; import AlertModal from "@ui/src/components/common/ConditionalActionModal/AlertModal"; -import { notify } from "@ui/index"; +import { ReactNode } from "react"; interface ReservationModalProps { isOpen: boolean; onClose: () => void; onConfirm: () => void; + title: string; // 추가 + content: ReactNode; // 추가 + cancelButtonName: string; // 추가 + confirmButtonName: string; // 추가 } -export default function ReservationModal({ isOpen, onClose, onConfirm }: ReservationModalProps): JSX.Element { +export default function ReservationModal({ + isOpen, + onClose, + onConfirm, + title, + content, + cancelButtonName, + confirmButtonName, +}: ReservationModalProps): JSX.Element { return ( { onConfirm(); }} - title="회의실을 예약하시겠어요?" - content={<>선택한 시간대의 회의실이 예약됩니다.} - cancelButtonName="취소하기" - confirmButtonName="예약하기" + title={title} + content={content} + cancelButtonName={cancelButtonName} + confirmButtonName={confirmButtonName} /> ); } From e01710045c96aca2dfd848d67a057dfa7d880318 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 17:37:27 +0900 Subject: [PATCH 42/72] =?UTF-8?q?Feat:=20ReservationSheetContent=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 76 +--------- .../Reservation/MobileReservationSheet.tsx | 61 +++----- .../Reservation/ReservationSheetContent.tsx | 140 ++++++++++++++++++ 3 files changed, 165 insertions(+), 112 deletions(-) create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index 214fa4bd..b6901944 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -1,16 +1,9 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { type IReservation } from "@repo/types"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { notify } from "@ui/index"; -import Sidebar from "@/components/common/Sidebar"; -import { type ScheduleFormData, type Schedule, type SelectedRoom } from "@/app/types/scheduletypes"; +import { useEffect } from "react"; import { useSidebarStore } from "@/app/store/useSidebarStore"; -import { createReservation, updateReservation, type CreateReservationRequest } from "@/api/reservations"; -import ReservationForm from "./ReservationForm"; -import ReservationModal from "./ReservationModal"; -import { useAuthStore } from "@/src/stores/useAuthStore"; +import Sidebar from "@/components/common/Sidebar"; +import ReservationSheetContent from "./ReservationSheetContent"; +import { type IReservation } from "@repo/types"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; interface DesktopReservationSheetProps { onClose: () => void; @@ -21,49 +14,7 @@ interface DesktopReservationSheetProps { export default function DesktopReservationSheet(props: DesktopReservationSheetProps): JSX.Element { const { onClose, selectedTime, selectedSchedule, selectedRoom } = props; - const queryClient = useQueryClient(); const { isSidebarOpen, closeSidebar } = useSidebarStore(); - const [isModalOpen, setIsModalOpen] = useState(false); - - const user = useAuthStore((state) => state.user); - - const [formData, setFormData] = useState<{ - data: CreateReservationRequest; - itemId: string; - reservationId?: string; - } | null>(null); - - const isEditMode = !!selectedSchedule && selectedSchedule.user._id === user?._id; - - const createOrUpdateReservation = useMutation({ - mutationFn: (formData: { data: CreateReservationRequest; itemId: string; reservationId?: string }) => { - if (isEditMode && formData.reservationId) { - // 수정 모드일 경우 - return updateReservation(formData.reservationId, formData.data); - } else { - // 생성 모드일 경우 - return createReservation(formData.itemId, formData.data); - } - }, - onSuccess: async () => { - notify({ - type: "success", - message: isEditMode ? "예약이 수정되었습니다." : "회의실이 예약되었습니다.", - }); - await queryClient.invalidateQueries({ queryKey: ["reservation"] }); - setIsModalOpen(false); - onClose(); - }, - }); - - const handleSubmit = ( - data: CreateReservationRequest, - itemId: string, - reservationId?: string, // 수정 모드일 경우 reservationId 추가 - ): void => { - setFormData({ data, itemId, reservationId }); - setIsModalOpen(true); - }; useEffect(() => { if (!isSidebarOpen) { @@ -80,26 +31,13 @@ export default function DesktopReservationSheet(props: DesktopReservationSheetPr onClose(); }} > - - { - setIsModalOpen(false); - }} - onConfirm={() => { - if (formData) { - createOrUpdateReservation.mutate(formData); - } - setIsModalOpen(false); - onClose(); - }} - />
); } diff --git a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx index 20ca17cd..61483b56 100644 --- a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx @@ -1,10 +1,7 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { Sheet } from "react-modal-sheet"; -import { useState } from "react"; +import ReservationSheetContent from "./ReservationSheetContent"; import { type IReservation } from "@repo/types"; -import { type ScheduleFormData, type Schedule, type SelectedRoom } from "@/app/types/scheduletypes"; -import ReservationForm from "./ReservationForm"; -import ReservationModal from "./ReservationModal"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; interface MobileReservationSheetProps { isOpen: boolean; @@ -14,45 +11,23 @@ interface MobileReservationSheetProps { selectedRoom?: SelectedRoom | null; } -export default function MobileReservationSheet({ - isOpen, - onClose, - selectedTime, - selectedSchedule, - selectedRoom, -}: MobileReservationSheetProps): JSX.Element { - const [isModalOpen, setIsModalOpen] = useState(false); - - const handleSubmit = (data: ScheduleFormData): void => { - setIsModalOpen(true); - }; +export default function MobileReservationSheet(props: MobileReservationSheetProps): JSX.Element { + const { isOpen, onClose, selectedTime, selectedSchedule, selectedRoom } = props; return ( - <> - - - - - - - - - - { - setIsModalOpen(false); - }} - onConfirm={() => { - setIsModalOpen(false); - onClose(); - }} - /> - + + + + + + + + + ); } diff --git a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx new file mode 100644 index 00000000..c6531cec --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx @@ -0,0 +1,140 @@ +import { useEffect, useState } from "react"; +import { type IReservation } from "@repo/types"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { notify } from "@ui/index"; +import ReservationForm from "./ReservationForm"; +import ReservationModal from "./ReservationModal"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; +import { + createReservation, + deleteReservation, + updateReservation, + type CreateReservationRequest, +} from "@/api/reservations"; +import { useAuthStore } from "@/src/stores/useAuthStore"; + +interface ReservationSheetContentProps { + onClose: () => void; + selectedTime: string; + selectedSchedule?: IReservation | null; + selectedRoom?: SelectedRoom | null; +} + +export default function ReservationSheetContent(props: ReservationSheetContentProps): JSX.Element { + const { onClose, selectedTime, selectedSchedule, selectedRoom } = props; + const queryClient = useQueryClient(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalType, setModalType] = useState<"create/update" | "delete">("create/update"); + + const user = useAuthStore((state) => state.user); + + const [formData, setFormData] = useState<{ + data: CreateReservationRequest; + itemId: string; + reservationId?: string; + } | null>(null); + + const isEditMode = !!selectedSchedule && selectedSchedule.user._id === user?._id; + + const createOrUpdateReservation = useMutation({ + mutationFn: (formData: { data: CreateReservationRequest; itemId: string; reservationId?: string }) => { + if (isEditMode && formData.reservationId) { + // 수정 모드일 경우 + return updateReservation(formData.reservationId, formData.data); + } else { + // 생성 모드일 경우 + return createReservation(formData.itemId, formData.data); + } + }, + onSuccess: async () => { + notify({ + type: "success", + message: isEditMode ? "예약이 수정되었습니다." : "회의실이 예약되었습니다.", + }); + await queryClient.invalidateQueries({ queryKey: ["reservation"] }); + setIsModalOpen(false); + onClose(); + }, + }); + + const deleteReservationMutation = useMutation({ + mutationFn: (reservationId: string) => deleteReservation(reservationId), + onSuccess: async () => { + notify({ + type: "success", + message: "예약이 삭제되었습니다.", + }); + await queryClient.invalidateQueries({ queryKey: ["reservation"] }); + setIsModalOpen(false); + onClose(); + }, + }); + + const handleSubmit = (data: CreateReservationRequest, itemId: string, reservationId?: string): void => { + setFormData({ data, itemId, reservationId }); + setModalType("create/update"); // 모달 타입 설정 + setIsModalOpen(true); + }; + + const handleDelete = (): void => { + setModalType("delete"); // 모달 타입 설정 + setIsModalOpen(true); + }; + + const confirmDelete = (): void => { + if (selectedSchedule?._id) { + deleteReservationMutation.mutate(selectedSchedule._id); + } + }; + + const modalTitle = + modalType === "delete" + ? "예약을 삭제하시겠어요?" + : isEditMode + ? "회의실 예약을 수정하시겠어요?" + : "회의실을 예약하시겠어요?"; + + const modalContent = + modalType === "delete" ? ( + <>선택한 예약이 삭제됩니다. + ) : isEditMode ? ( + <>선택한 시간대의 회의실 예약이 수정됩니다. + ) : ( + <>선택한 시간대의 회의실이 예약됩니다. + ); + + const modalConfirmButtonName = modalType === "delete" ? "삭제하기" : isEditMode ? "수정하기" : "예약하기"; + + return ( + <> + + { + setIsModalOpen(false); + }} + onConfirm={() => { + if (modalType === "create/update") { + if (formData) { + createOrUpdateReservation.mutate(formData); + } + } else if (modalType === "delete") { + confirmDelete(); + } + setIsModalOpen(false); + onClose(); + }} + title={modalTitle} + content={modalContent} + cancelButtonName="취소하기" + confirmButtonName={modalConfirmButtonName} + /> + + ); +} From 51302b2e955b87234dbbf7b972bead7ac8a43437 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:02:05 +0900 Subject: [PATCH 43/72] =?UTF-8?q?Feat:=20invalidateQueries=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=ED=99=94(=EC=83=9D=EC=84=B1,=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=8B=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationSheetContent.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx index c6531cec..5a42422b 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx @@ -12,6 +12,7 @@ import { type CreateReservationRequest, } from "@/api/reservations"; import { useAuthStore } from "@/src/stores/useAuthStore"; +import { useDateStore } from "@/app/store/useDateStore"; interface ReservationSheetContentProps { onClose: () => void; @@ -27,6 +28,14 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr const [modalType, setModalType] = useState<"create/update" | "delete">("create/update"); const user = useAuthStore((state) => state.user); + const { selectedDate } = useDateStore(); + + const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart( + 2, + "0", + )}-${String(selectedDate.day).padStart(2, "0")}`; + + const MeetingRoomsType = "room"; const [formData, setFormData] = useState<{ data: CreateReservationRequest; @@ -51,7 +60,9 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr type: "success", message: isEditMode ? "예약이 수정되었습니다." : "회의실이 예약되었습니다.", }); - await queryClient.invalidateQueries({ queryKey: ["reservation"] }); + await queryClient.invalidateQueries({ + queryKey: ["meetings", formattedDate, MeetingRoomsType], + }); setIsModalOpen(false); onClose(); }, @@ -64,7 +75,9 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr type: "success", message: "예약이 삭제되었습니다.", }); - await queryClient.invalidateQueries({ queryKey: ["reservation"] }); + await queryClient.invalidateQueries({ + queryKey: ["meetings", formattedDate, MeetingRoomsType], + }); setIsModalOpen(false); onClose(); }, From baa7c99a1596ad7e85b8343977c9831aefa96ce2 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:04:20 +0900 Subject: [PATCH 44/72] =?UTF-8?q?Feat:=20ReservationForm=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=ED=95=98=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index bcc5b729..8838d2a8 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -25,16 +25,17 @@ interface ReservationFormProps { selectedSchedule?: IReservation | null; resetTrigger?: number; selectedRoom?: SelectedRoom | null; + onDelete: () => void; } export default function ReservationForm(props: ReservationFormProps): JSX.Element { - const { onSubmit, selectedTime, resetTrigger, selectedRoom, selectedSchedule } = props; + const { onSubmit, selectedTime, resetTrigger, selectedRoom, selectedSchedule, onDelete } = props; const MeetingRoomsType = "room"; // 현재 사용자 데이터 가져오기 const user = useAuthStore((state) => state.user); - const isEditMode = !!selectedSchedule && selectedSchedule.user._id === user?._id; + const isEditMode = Boolean(selectedSchedule) && selectedSchedule.user._id === user?._id; const { selectedDate } = useDateStore(); @@ -317,7 +318,7 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen // 상태에 저장하여 화면에 표시 setSubmittedData(mappedData); - onSubmit(mappedData, selectedMeetingRoom!._id, selectedSchedule?._id); + onSubmit(mappedData, selectedMeetingRoom._id, selectedSchedule?._id); }; return ( @@ -492,13 +493,22 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen )} /> - + {/* 삭제하기 버튼 (수정 모드일 때만 표시) */} + {isEditMode ? : null} {/* 예약하기 버튼 */} From 7acf60fe877bbd056e4c244863ed3ee021c6887b Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:22:33 +0900 Subject: [PATCH 45/72] =?UTF-8?q?Test:=20=EC=98=88=EC=95=BD=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EB=93=A4=20text=EB=A1=9C=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=B4=EB=B3=B4=EA=B8=B0(=EC=9E=84=EC=8B=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/meetings/_components/main.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/web/app/meetings/_components/main.tsx b/apps/web/app/meetings/_components/main.tsx index 782e695e..e20a925d 100644 --- a/apps/web/app/meetings/_components/main.tsx +++ b/apps/web/app/meetings/_components/main.tsx @@ -51,6 +51,13 @@ export default function MeetingRoomSchedule(): JSX.Element { 시작 시간:{" "} {new Date(meeting.startAt).toLocaleString("ko-KR", { timeZone: "Asia/Seoul", + hour12: false, // 24시간 형식으로 변경 + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + year: "numeric", + month: "2-digit", + day: "2-digit", })}

From fe70af3873385c933f752b3dad6877fdef932d04 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:25:45 +0900 Subject: [PATCH 46/72] =?UTF-8?q?Fix:=20type=20=20=EB=B0=8F=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/DesktopReservationSheet.tsx | 4 ++-- .../_components/Reservation/MobileReservationSheet.tsx | 2 +- .../_components/Reservation/ReservationModal.tsx | 2 +- .../Reservation/ReservationSheetContent.tsx | 10 +++++----- .../_components/Schedule/ScheduleRow/ScheduleSlot.tsx | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx index b6901944..9c4f9db0 100644 --- a/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/DesktopReservationSheet.tsx @@ -1,9 +1,9 @@ import { useEffect } from "react"; +import { type IReservation } from "@repo/types"; import { useSidebarStore } from "@/app/store/useSidebarStore"; import Sidebar from "@/components/common/Sidebar"; -import ReservationSheetContent from "./ReservationSheetContent"; -import { type IReservation } from "@repo/types"; import { type SelectedRoom } from "@/app/types/scheduletypes"; +import ReservationSheetContent from "./ReservationSheetContent"; interface DesktopReservationSheetProps { onClose: () => void; diff --git a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx index 61483b56..01cad395 100644 --- a/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx +++ b/apps/web/app/meetings/_components/Reservation/MobileReservationSheet.tsx @@ -1,7 +1,7 @@ import { Sheet } from "react-modal-sheet"; -import ReservationSheetContent from "./ReservationSheetContent"; import { type IReservation } from "@repo/types"; import { type SelectedRoom } from "@/app/types/scheduletypes"; +import ReservationSheetContent from "./ReservationSheetContent"; interface MobileReservationSheetProps { isOpen: boolean; diff --git a/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx b/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx index d625035c..075f51cd 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationModal.tsx @@ -1,7 +1,7 @@ "use client"; import AlertModal from "@ui/src/components/common/ConditionalActionModal/AlertModal"; -import { ReactNode } from "react"; +import { type ReactNode } from "react"; interface ReservationModalProps { isOpen: boolean; diff --git a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx index 5a42422b..d3d5026e 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx @@ -2,8 +2,6 @@ import { useEffect, useState } from "react"; import { type IReservation } from "@repo/types"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { notify } from "@ui/index"; -import ReservationForm from "./ReservationForm"; -import ReservationModal from "./ReservationModal"; import { type SelectedRoom } from "@/app/types/scheduletypes"; import { createReservation, @@ -13,6 +11,8 @@ import { } from "@/api/reservations"; import { useAuthStore } from "@/src/stores/useAuthStore"; import { useDateStore } from "@/app/store/useDateStore"; +import ReservationModal from "./ReservationModal"; +import ReservationForm from "./ReservationForm"; interface ReservationSheetContentProps { onClose: () => void; @@ -43,17 +43,17 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr reservationId?: string; } | null>(null); - const isEditMode = !!selectedSchedule && selectedSchedule.user._id === user?._id; + const isEditMode = Boolean(selectedSchedule) && selectedSchedule.user._id === user?._id; const createOrUpdateReservation = useMutation({ mutationFn: (formData: { data: CreateReservationRequest; itemId: string; reservationId?: string }) => { if (isEditMode && formData.reservationId) { // 수정 모드일 경우 return updateReservation(formData.reservationId, formData.data); - } else { + } // 생성 모드일 경우 return createReservation(formData.itemId, formData.data); - } + }, onSuccess: async () => { notify({ diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx index 5ca2ba87..34ae3cb1 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx @@ -1,9 +1,9 @@ // ScheduleSlot.tsx import { useEffect, useState } from "react"; +import { type IReservation } from "@repo/types"; import { useSidebarStore } from "@/app/store/useSidebarStore"; import { useAuthStore } from "@/src/stores/useAuthStore"; // 현재 사용자 정보를 가져오기 위해 추가 -import { type IReservation } from "@repo/types"; interface ScheduleSlotProps { index: number; @@ -51,7 +51,7 @@ export default function ScheduleSlot(props: ScheduleSlotProps): JSX.Element { {/* 예약된 슬롯의 경우 중간에 가로 막대를 표시 */} {isReserved && schedule ? (

-
+
) : null} From f34e28e0afb19c0c9fbf0e407a0b49c4bc7b7342 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:42:10 +0900 Subject: [PATCH 47/72] =?UTF-8?q?Feat:=20useRoomsData=20hooks=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_hooks/useRoomsData.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 apps/web/app/_hooks/useRoomsData.ts diff --git a/apps/web/app/_hooks/useRoomsData.ts b/apps/web/app/_hooks/useRoomsData.ts new file mode 100644 index 00000000..54fbb513 --- /dev/null +++ b/apps/web/app/_hooks/useRoomsData.ts @@ -0,0 +1,11 @@ +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { type TBaseItem } from "@repo/types"; +import { getAllItems } from "@/api/items"; +import { MEETING_ROOMS_TYPE } from "../constants/meetingRoomsType"; + +export const useRoomsData = (itemType: string): UseQueryResult => { + return useQuery({ + queryKey: ["Rooms", itemType], + queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), + }); +}; From 3a13addf20775d04b634645ac19021f00f756d37 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:43:44 +0900 Subject: [PATCH 48/72] =?UTF-8?q?Feat:=20useMeetingsData=20hook=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_hooks/useMeetingsData.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 apps/web/app/_hooks/useMeetingsData.ts diff --git a/apps/web/app/_hooks/useMeetingsData.ts b/apps/web/app/_hooks/useMeetingsData.ts new file mode 100644 index 00000000..abdb6e9b --- /dev/null +++ b/apps/web/app/_hooks/useMeetingsData.ts @@ -0,0 +1,11 @@ +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { type IReservation } from "@repo/types/src/reservationType"; +import { getReservationsByTypeAndDate } from "@/api/reservations"; +import { MEETING_ROOMS_TYPE } from "../constants/meetingRoomsType"; + +export const useMeetingsData = (itemType: string, date: string): UseQueryResult => { + return useQuery({ + queryKey: ["meetings", date, itemType], + queryFn: () => getReservationsByTypeAndDate({ itemType: MEETING_ROOMS_TYPE, date }), + }); +}; From f40963b3a3a6bd357fe941c1ca05cd70648d0716 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:45:01 +0900 Subject: [PATCH 49/72] =?UTF-8?q?Feat:=20meetingroomstype=20=EC=83=81?= =?UTF-8?q?=EC=88=98=ED=99=94=20=EB=B0=8F=20formatDate=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/constants/meetingRoomsType.ts | 1 + apps/web/app/constants/timeOptions.ts | 1 - apps/web/app/utils/formatDate.ts | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 apps/web/app/constants/meetingRoomsType.ts create mode 100644 apps/web/app/utils/formatDate.ts diff --git a/apps/web/app/constants/meetingRoomsType.ts b/apps/web/app/constants/meetingRoomsType.ts new file mode 100644 index 00000000..080b4570 --- /dev/null +++ b/apps/web/app/constants/meetingRoomsType.ts @@ -0,0 +1 @@ +export const MEETING_ROOMS_TYPE = "room"; diff --git a/apps/web/app/constants/timeOptions.ts b/apps/web/app/constants/timeOptions.ts index 696af9c7..ccf2d5d8 100644 --- a/apps/web/app/constants/timeOptions.ts +++ b/apps/web/app/constants/timeOptions.ts @@ -47,5 +47,4 @@ export const timeOptions = [ "22:30", "23:00", "23:30", - "23:40", ]; diff --git a/apps/web/app/utils/formatDate.ts b/apps/web/app/utils/formatDate.ts new file mode 100644 index 00000000..4646a8fa --- /dev/null +++ b/apps/web/app/utils/formatDate.ts @@ -0,0 +1,3 @@ +export function formatDate(date: { year: number; month: number; day: number }): string { + return `${String(date.year)}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}`; +} From 8e1d62ff48b7afb4cd98794f406686eb22191382 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 21 Nov 2024 18:56:32 +0900 Subject: [PATCH 50/72] =?UTF-8?q?Refactor:=20MeetingRoomSchedule=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_hooks/useMeetingsData.ts | 2 +- apps/web/app/_hooks/useRoomsData.ts | 2 +- .../_components/MeetingRoomSchedule.tsx | 22 ++++ apps/web/app/meetings/_components/main.tsx | 100 ------------------ apps/web/app/meetings/page.tsx | 4 +- 5 files changed, 26 insertions(+), 104 deletions(-) create mode 100644 apps/web/app/meetings/_components/MeetingRoomSchedule.tsx delete mode 100644 apps/web/app/meetings/_components/main.tsx diff --git a/apps/web/app/_hooks/useMeetingsData.ts b/apps/web/app/_hooks/useMeetingsData.ts index abdb6e9b..d3c44069 100644 --- a/apps/web/app/_hooks/useMeetingsData.ts +++ b/apps/web/app/_hooks/useMeetingsData.ts @@ -3,7 +3,7 @@ import { type IReservation } from "@repo/types/src/reservationType"; import { getReservationsByTypeAndDate } from "@/api/reservations"; import { MEETING_ROOMS_TYPE } from "../constants/meetingRoomsType"; -export const useMeetingsData = (itemType: string, date: string): UseQueryResult => { +export const useMeetingsData = (itemType: string, date: string): UseQueryResult => { return useQuery({ queryKey: ["meetings", date, itemType], queryFn: () => getReservationsByTypeAndDate({ itemType: MEETING_ROOMS_TYPE, date }), diff --git a/apps/web/app/_hooks/useRoomsData.ts b/apps/web/app/_hooks/useRoomsData.ts index 54fbb513..9e237777 100644 --- a/apps/web/app/_hooks/useRoomsData.ts +++ b/apps/web/app/_hooks/useRoomsData.ts @@ -3,7 +3,7 @@ import { type TBaseItem } from "@repo/types"; import { getAllItems } from "@/api/items"; import { MEETING_ROOMS_TYPE } from "../constants/meetingRoomsType"; -export const useRoomsData = (itemType: string): UseQueryResult => { +export const useRoomsData = (itemType: string): UseQueryResult => { return useQuery({ queryKey: ["Rooms", itemType], queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), diff --git a/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx b/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx new file mode 100644 index 00000000..3bb33403 --- /dev/null +++ b/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useDateStore } from "@/app/store/useDateStore"; +import { useMeetingsData } from "@/app/_hooks/useMeetingsData"; +import { useRoomsData } from "@/app/_hooks/useRoomsData"; +import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; +import { formatDate } from "@/app/utils/formatDate"; +import ScheduleTable from "./Schedule/ScheduleTable"; + +export default function MeetingRoomSchedule(): JSX.Element { + const { selectedDate } = useDateStore(); + + const formattedDate = formatDate(selectedDate); + + const { data: meetingsData = [], isLoading: meetingsIsLoading } = useMeetingsData(MEETING_ROOMS_TYPE, formattedDate); + + const { data: roomsData = [], isLoading: roomsIsLoading } = useRoomsData(MEETING_ROOMS_TYPE); + + if (meetingsIsLoading || roomsIsLoading) return
로딩~
; + + return ; +} diff --git a/apps/web/app/meetings/_components/main.tsx b/apps/web/app/meetings/_components/main.tsx deleted file mode 100644 index e20a925d..00000000 --- a/apps/web/app/meetings/_components/main.tsx +++ /dev/null @@ -1,100 +0,0 @@ -"use client"; - -import { useQuery } from "@tanstack/react-query"; -import { type IReservation } from "@repo/types/src/reservationType"; -import { type TBaseItem } from "@repo/types"; -import { useDateStore } from "@/app/store/useDateStore"; -import { getReservationsByTypeAndDate } from "@/api/reservations"; -import { getAllItems } from "@/api/items"; -import ScheduleTable from "./Schedule/ScheduleTable"; - -export default function MeetingRoomSchedule(): JSX.Element { - const { selectedDate } = useDateStore(); - - const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart( - 2, - "0", - )}-${String(selectedDate.day).padStart(2, "0")}`; - - const MeetingRoomsType = "room"; - - const { data: meetingsData = [], isLoading: meetingsIsLoading } = useQuery({ - queryKey: ["meetings", formattedDate, MeetingRoomsType], - queryFn: () => getReservationsByTypeAndDate({ itemType: MeetingRoomsType, date: formattedDate }), - }); - - const { data: roomsData = [], isLoading: roomsIsLoading } = useQuery({ - queryKey: ["Rooms", MeetingRoomsType], - queryFn: () => getAllItems({ itemType: MeetingRoomsType }), - }); - - if (meetingsIsLoading || roomsIsLoading) return
로딩중이에요~
; - - return ( - <> - -
- {meetingsData.length > 0 ? ( - meetingsData.map((meeting) => ( -
-

예약자: {meeting.user.name || "알 수 없음"}

-

회의실: {typeof meeting.item === "string" ? meeting.item : meeting.item.name || "알 수 없음"}

-

- 시작 시간:{" "} - {new Date(meeting.startAt).toLocaleString("ko-KR", { - timeZone: "Asia/Seoul", - hour12: false, // 24시간 형식으로 변경 - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - year: "numeric", - month: "2-digit", - day: "2-digit", - })} -

-

- 종료 시간:{" "} - {new Date(meeting.endAt).toLocaleString("ko-KR", { - timeZone: "Asia/Seoul", - })} -

-

상태: {meeting.status}

- {meeting.notes ?

메모: {meeting.notes}

: null} -
- )) - ) : ( -

예약된 회의가 없습니다. {formattedDate}

- )} -
-
- {roomsData.length > 0 ? ( - roomsData.map((room) => ( -
-

회의실 이름: {room.name}

-

상태: {room.status}

-

설명: {room.description ?? "없음"}

-
- )) - ) : ( -

등록된 회의실이 없습니다.

- )} -
- - ); -} diff --git a/apps/web/app/meetings/page.tsx b/apps/web/app/meetings/page.tsx index 975a8175..491de61a 100644 --- a/apps/web/app/meetings/page.tsx +++ b/apps/web/app/meetings/page.tsx @@ -1,5 +1,5 @@ -import Main from "./_components/main"; +import MeetingRoomSchedule from "./_components/MeetingRoomSchedule"; export default function Meetings(): JSX.Element { - return
; + return ; } From 470e286bb200af28653249996e1692ef67330261 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 15:48:13 +0900 Subject: [PATCH 51/72] Remove: query hook delete --- apps/web/app/_hooks/useMeetingsData.ts | 11 ----------- apps/web/app/_hooks/useRoomsData.ts | 11 ----------- .../meetings/_components/MeetingRoomSchedule.tsx | 16 ++++++++++++---- 3 files changed, 12 insertions(+), 26 deletions(-) delete mode 100644 apps/web/app/_hooks/useMeetingsData.ts delete mode 100644 apps/web/app/_hooks/useRoomsData.ts diff --git a/apps/web/app/_hooks/useMeetingsData.ts b/apps/web/app/_hooks/useMeetingsData.ts deleted file mode 100644 index d3c44069..00000000 --- a/apps/web/app/_hooks/useMeetingsData.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery, type UseQueryResult } from "@tanstack/react-query"; -import { type IReservation } from "@repo/types/src/reservationType"; -import { getReservationsByTypeAndDate } from "@/api/reservations"; -import { MEETING_ROOMS_TYPE } from "../constants/meetingRoomsType"; - -export const useMeetingsData = (itemType: string, date: string): UseQueryResult => { - return useQuery({ - queryKey: ["meetings", date, itemType], - queryFn: () => getReservationsByTypeAndDate({ itemType: MEETING_ROOMS_TYPE, date }), - }); -}; diff --git a/apps/web/app/_hooks/useRoomsData.ts b/apps/web/app/_hooks/useRoomsData.ts deleted file mode 100644 index 9e237777..00000000 --- a/apps/web/app/_hooks/useRoomsData.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery, type UseQueryResult } from "@tanstack/react-query"; -import { type TBaseItem } from "@repo/types"; -import { getAllItems } from "@/api/items"; -import { MEETING_ROOMS_TYPE } from "../constants/meetingRoomsType"; - -export const useRoomsData = (itemType: string): UseQueryResult => { - return useQuery({ - queryKey: ["Rooms", itemType], - queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), - }); -}; diff --git a/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx b/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx index 3bb33403..ae189c1b 100644 --- a/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx +++ b/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx @@ -1,10 +1,12 @@ "use client"; +import { type IReservation, type TBaseItem } from "@repo/types"; +import { useQuery } from "@tanstack/react-query"; import { useDateStore } from "@/app/store/useDateStore"; -import { useMeetingsData } from "@/app/_hooks/useMeetingsData"; -import { useRoomsData } from "@/app/_hooks/useRoomsData"; import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; import { formatDate } from "@/app/utils/formatDate"; +import { getAllItems } from "@/api/items"; +import { getReservationsByTypeAndDate } from "@/api/reservations"; import ScheduleTable from "./Schedule/ScheduleTable"; export default function MeetingRoomSchedule(): JSX.Element { @@ -12,9 +14,15 @@ export default function MeetingRoomSchedule(): JSX.Element { const formattedDate = formatDate(selectedDate); - const { data: meetingsData = [], isLoading: meetingsIsLoading } = useMeetingsData(MEETING_ROOMS_TYPE, formattedDate); + const { data: meetingsData = [], isLoading: meetingsIsLoading } = useQuery({ + queryKey: ["meetings", formattedDate, MEETING_ROOMS_TYPE], + queryFn: () => getReservationsByTypeAndDate({ itemType: MEETING_ROOMS_TYPE, date: formattedDate }), + }); - const { data: roomsData = [], isLoading: roomsIsLoading } = useRoomsData(MEETING_ROOMS_TYPE); + const { data: roomsData = [], isLoading: roomsIsLoading } = useQuery({ + queryKey: ["Rooms", MEETING_ROOMS_TYPE], + queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), + }); if (meetingsIsLoading || roomsIsLoading) return
로딩~
; From 68f752532f67c744bc39bf199908b45b708744e0 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 16:13:21 +0900 Subject: [PATCH 52/72] =?UTF-8?q?Feat:=20getRoomSchedules=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleTable/ScheduleTableDesktop.tsx | 12 ++---------- .../ScheduleTable/ScheduleTableMobile.tsx | 4 +--- apps/web/app/utils/getRoomSchedules.ts | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 13 deletions(-) create mode 100644 apps/web/app/utils/getRoomSchedules.ts diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index 98703077..9d9c598f 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -1,6 +1,7 @@ "use client"; import { type TBaseItem, type IReservation } from "@repo/types"; +import { getRoomSchedules } from "@/app/utils/getRoomSchedules"; import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import CurrentTimeIndicator from "../ScheduleRow/CurrentTimeIndicator"; @@ -28,16 +29,7 @@ export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps):
{rooms.map((room) => { - const roomSchedules = meetingsData.filter((schedule) => { - const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; - - const isSameRoom = scheduleItemId === room._id; - - const scheduleDate = new Date(schedule.startAt).toLocaleDateString("en-CA", { timeZone: "Asia/Seoul" }); - const isSameDate = scheduleDate === selectedDate; - - return isSameRoom && isSameDate; - }); + const roomSchedules = getRoomSchedules(room, meetingsData, selectedDate); return (
diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx index e4a277c9..9c73a3de 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx @@ -12,8 +12,6 @@ interface ScheduleTableMobileProps { selectedDate: string; } -// ScheduleTableMobile.tsx - export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JSX.Element { const { rooms, meetingsData, selectedDate } = props; @@ -39,7 +37,7 @@ export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JS
diff --git a/apps/web/app/utils/getRoomSchedules.ts b/apps/web/app/utils/getRoomSchedules.ts new file mode 100644 index 00000000..d99a12e7 --- /dev/null +++ b/apps/web/app/utils/getRoomSchedules.ts @@ -0,0 +1,15 @@ +import { type IReservation } from "@repo/types/src/reservationType"; +import { type TBaseItem } from "@repo/types"; + +export function getRoomSchedules(room: TBaseItem, meetingsData: IReservation[], selectedDate: string): IReservation[] { + return meetingsData.filter((schedule) => { + const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; + + const isSameRoom = scheduleItemId === room._id; + + const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; + const isSameDate = scheduleDate === selectedDate; + + return isSameRoom && isSameDate; + }); +} From 50de238ced0ea03529247ad56761237f56a2db77 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 16:24:39 +0900 Subject: [PATCH 53/72] =?UTF-8?q?Feat:=20RoomName=20children=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/meetings/_components/Schedule/RoomName.tsx | 8 +++++--- .../ScheduleTable/ScheduleTableDesktop.tsx | 2 +- .../Schedule/ScheduleTable/ScheduleTableMobile.tsx | 14 +++----------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/RoomName.tsx b/apps/web/app/meetings/_components/Schedule/RoomName.tsx index 11df9781..e3a522b3 100644 --- a/apps/web/app/meetings/_components/Schedule/RoomName.tsx +++ b/apps/web/app/meetings/_components/Schedule/RoomName.tsx @@ -1,15 +1,17 @@ "use client"; +import { type ReactNode } from "react"; + interface RoomNameProps { - name: string; + children: ReactNode; } export default function RoomName(props: RoomNameProps): JSX.Element { - const { name } = props; + const { children } = props; return (
- {name} + {children}
); } diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index 9d9c598f..45bc9810 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -22,7 +22,7 @@ export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps):
{rooms.map((room) => (
- + {room.name}
))}
diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx index 9c73a3de..5d1473cd 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx @@ -2,6 +2,7 @@ import { type TBaseItem } from "@repo/types"; import { type IReservation } from "@repo/types/src/reservationType"; +import { getRoomSchedules } from "@/app/utils/getRoomSchedules"; import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import TimeText from "../ScheduleRow/TimeText"; @@ -18,20 +19,11 @@ export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JS return (
{rooms.map((room) => { - const roomSchedules = meetingsData.filter((schedule) => { - const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; - - const isSameRoom = scheduleItemId === room._id; - - const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; - const isSameDate = scheduleDate === selectedDate; - - return isSameRoom && isSameDate; - }); + const roomSchedules = getRoomSchedules(room, meetingsData, selectedDate); return (
- + {room.name}
From 4c49e9aefe76dea248aab88d73240e4c6e37dc70 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 16:29:26 +0900 Subject: [PATCH 54/72] =?UTF-8?q?Feat:=20CurrentTimeLabel=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_hooks/useCurrentTimePosition.ts | 54 +++++++++++++++++++ .../ScheduleRow/CurrentTimeIndicator.tsx | 54 ++++--------------- .../Schedule/ScheduleRow/CurrentTimeLabel.tsx | 16 ++++++ 3 files changed, 80 insertions(+), 44 deletions(-) create mode 100644 apps/web/app/_hooks/useCurrentTimePosition.ts create mode 100644 apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx diff --git a/apps/web/app/_hooks/useCurrentTimePosition.ts b/apps/web/app/_hooks/useCurrentTimePosition.ts new file mode 100644 index 00000000..e9ee2a18 --- /dev/null +++ b/apps/web/app/_hooks/useCurrentTimePosition.ts @@ -0,0 +1,54 @@ +import { useEffect, useState } from "react"; + +interface UseCurrentTimePositionProps { + slotWidth: number; + startHour: number; + endHour: number; +} + +interface UseCurrentTimePositionReturn { + currentPosition: number | null; + currentTime: string; +} + +export function useCurrentTimePosition({ + slotWidth, + startHour, + endHour, +}: UseCurrentTimePositionProps): UseCurrentTimePositionReturn { + const [currentPosition, setCurrentPosition] = useState(null); + const [currentTime, setCurrentTime] = useState(""); + + useEffect(() => { + const updatePosition = (): void => { + const now = new Date(); + const currentMinutes = now.getHours() * 60 + now.getMinutes(); + const totalMinutes = (endHour - startHour) * 60; + + if (currentMinutes < startHour * 60 || currentMinutes >= endHour * 60) { + setCurrentPosition(null); + setCurrentTime(""); + return; + } + + const relativeMinutes = currentMinutes - startHour * 60; + const scheduleWidth = slotWidth * (endHour - startHour) * 2; // Assuming 30-minute slots + const position = (relativeMinutes / totalMinutes) * scheduleWidth; + + setCurrentPosition(position); + + const hours = String(now.getHours()).padStart(2, "0"); + const minutes = String(now.getMinutes()).padStart(2, "0"); + setCurrentTime(`${hours}:${minutes}`); + }; + + updatePosition(); + const interval = setInterval(updatePosition, 60000); + + return () => { + clearInterval(interval); + }; + }, [slotWidth, startHour, endHour]); + + return { currentPosition, currentTime }; +} diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx index 98627b55..a392a1e2 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx @@ -1,6 +1,7 @@ "use client"; -import { useEffect, useState } from "react"; +import { useCurrentTimePosition } from "@/app/_hooks/useCurrentTimePosition"; +import CurrentTimeLabel from "./CurrentTimeLabel"; interface CurrentTimeIndicatorProps { slotWidth: number; @@ -10,58 +11,23 @@ interface CurrentTimeIndicatorProps { export default function CurrentTimeIndicator(props: CurrentTimeIndicatorProps): JSX.Element { const { slotWidth, startHour, endHour } = props; - const [currentPosition, setCurrentPosition] = useState(null); - const [currentTime, setCurrentTime] = useState(""); - - useEffect(() => { - const updatePosition = (): void => { - const now = new Date(); - const currentMinutes = now.getHours() * 60 + now.getMinutes(); - const totalMinutes = (endHour - startHour) * 60; - - if (currentMinutes < startHour * 60 || currentMinutes >= endHour * 60) { - setCurrentPosition(null); - setCurrentTime(""); - return; - } - - const relativeMinutes = currentMinutes - startHour * 60; - const totalSlots = (endHour - startHour) * 2; - const scheduleWidth = slotWidth * totalSlots; - const position = (relativeMinutes / totalMinutes) * scheduleWidth; - - setCurrentPosition(position); - - const hours = String(now.getHours()).padStart(2, "0"); - const minutes = String(now.getMinutes()).padStart(2, "0"); - setCurrentTime(`${hours}:${minutes}`); - }; - - updatePosition(); - const interval = setInterval(updatePosition, 60000); - - return () => { - clearInterval(interval); - }; - }, [slotWidth, startHour, endHour]); + const { currentPosition, currentTime } = useCurrentTimePosition({ + slotWidth, + startHour, + endHour, + }); if (currentPosition === null) { return
; } return ( -
+
- -
- {currentTime} -
+
); } diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx new file mode 100644 index 00000000..f0d9a44e --- /dev/null +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeLabel.tsx @@ -0,0 +1,16 @@ +interface CurrentTimeLabelProps { + position: number; + time: string; +} + +export default function CurrentTimeLabel(props: CurrentTimeLabelProps): JSX.Element { + const { position, time } = props; + return ( +
+ {time} +
+ ); +} From 231f0d654623de4f3f86adf30ce121879f82e5cf Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 17:35:31 +0900 Subject: [PATCH 55/72] =?UTF-8?q?Feat:=20useSlotReservations=20hook=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/_hooks/useSlotReservations.ts | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/web/app/_hooks/useSlotReservations.ts diff --git a/apps/web/app/_hooks/useSlotReservations.ts b/apps/web/app/_hooks/useSlotReservations.ts new file mode 100644 index 00000000..86709bbc --- /dev/null +++ b/apps/web/app/_hooks/useSlotReservations.ts @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { useMemo } from "react"; +import { type IReservation } from "@repo/types"; +import { timeToMinutes } from "../utils/timeToMinutes"; + +/** + * 예약 데이터를 슬롯 인덱스에 매핑하는 커스텀 훅 + * @param schedules 예약 데이터 배열 + * @param startHour 시작 시간 + * @param endHour 종료 시간 + * @param minutesPerSlot 슬롯당 분 단위 + * @returns 슬롯별 예약 상태 배열 + */ +export const useSlotReservations = ( + schedules: IReservation[], + startHour: number, + endHour: number, + minutesPerSlot: number, +): (IReservation | null)[] => { + return useMemo(() => { + const totalSlots = (endHour - startHour) * 2; // 30분 단위 + const slotReservations: (IReservation | null)[] = Array(totalSlots).fill(null); + + schedules.forEach((schedule) => { + const startMinutes = timeToMinutes(schedule.startAt) - startHour * 60; + const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; + + const startIndex = Math.floor(startMinutes / minutesPerSlot); + const endIndex = Math.ceil(endMinutes / minutesPerSlot); + + for (let i = startIndex; i < endIndex; i++) { + if (i >= 0 && i < totalSlots) { + slotReservations[i] = schedule; + } + } + }); + + return slotReservations; + }, [schedules, startHour, endHour, minutesPerSlot]); +}; From c3fe489bef6aa730e03d2af4e9624c01de9d272b Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 17:37:51 +0900 Subject: [PATCH 56/72] =?UTF-8?q?Feat:=20timeToMinutes=20util=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/utils/timeToMinutes.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/web/app/utils/timeToMinutes.ts diff --git a/apps/web/app/utils/timeToMinutes.ts b/apps/web/app/utils/timeToMinutes.ts new file mode 100644 index 00000000..8e96a35e --- /dev/null +++ b/apps/web/app/utils/timeToMinutes.ts @@ -0,0 +1,14 @@ +import { parseISO } from "date-fns"; + +/** + * 주어진 시간(Date 객체 또는 ISO 문자열)을 분 단위로 변환합니다. + * @param time Date 객체 또는 ISO 문자열 + * @returns 분 단위의 숫자 + */ + +export const timeToMinutes = (time: Date | string): number => { + const date = typeof time === "string" ? parseISO(time) : time; + const hours = date.getHours(); + const minutes = date.getMinutes(); + return hours * 60 + minutes; +}; From 0a936013ed2341b57b22f6ca13acd310fcc01529 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 17:39:09 +0900 Subject: [PATCH 57/72] =?UTF-8?q?Refactor:=20ScheduleRow=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationSheets.tsx | 42 ++++++ .../Schedule/ScheduleRow/ScheduleSlots.tsx | 64 +++++++++ .../Schedule/ScheduleRow/index.tsx | 124 +++--------------- 3 files changed, 125 insertions(+), 105 deletions(-) create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx create mode 100644 apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlots.tsx diff --git a/apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx b/apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx new file mode 100644 index 00000000..eb08a7e1 --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationSheets.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { type IReservation } from "@repo/types"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; +import DesktopReservationSheet from "./DesktopReservationSheet"; +import MobileReservationSheet from "./MobileReservationSheet"; + +interface ReservationSheetsProps { + isOpen: boolean; + onClose: () => void; + selectedTime: string | null; + selectedSchedule: IReservation | null; + selectedRoom: SelectedRoom | null; +} + +export default function ReservationSheets(props: ReservationSheetsProps): JSX.Element { + const { isOpen, onClose, selectedTime, selectedSchedule, selectedRoom } = props; + if (!selectedTime || !selectedRoom) return
; + + return ( + <> +
+ +
+ +
+ +
+ + ); +} diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlots.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlots.tsx new file mode 100644 index 00000000..5a0ebe2c --- /dev/null +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlots.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { type IReservation } from "@repo/types"; +import ScheduleSlot from "./ScheduleSlot"; + +interface ScheduleSlotsProps { + slotReservations: (IReservation | null)[]; + slotWidth: number; + slotHeight: number; + onSlotClick: (index: number, schedule?: IReservation) => void; +} + +export default function ScheduleSlots(props: ScheduleSlotsProps): JSX.Element { + const { slotReservations, slotWidth, slotHeight, onSlotClick } = props; + const slots: JSX.Element[] = []; + const totalSlots = slotReservations.length; + + for (let i = 0; i < totalSlots; i++) { + const schedule = slotReservations[i]; + if (schedule) { + // 예약된 슬롯의 시작 위치를 찾기 위해 이전 슬롯을 확인 + if (i === 0 || slotReservations[i - 1] !== schedule) { + // 예약된 슬롯의 시작 + const start = i; + let end = i; + // 예약된 슬롯의 끝 위치를 찾음 + while (end < totalSlots && slotReservations[end] === schedule) { + end++; + } + slots.push( + { + onSlotClick(start, schedule); + }} + isReserved + schedule={schedule} + />, + ); + i = end - 1; // 다음 반복에서 end 위치부터 시작 + } + } else { + // 예약되지 않은 슬롯은 개별적으로 렌더링 + slots.push( + { + onSlotClick(i); + }} + isReserved={false} + />, + ); + } + } + + return <>{slots}; +} diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index d591cf76..3b048130 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -2,14 +2,12 @@ import { useState } from "react"; import { type IReservation } from "@repo/types"; -import { parseISO } from "date-fns"; import { useSidebarStore } from "@/app/store/useSidebarStore"; -import { useAuthStore } from "@/src/stores/useAuthStore"; import { type SelectedRoom } from "@/app/types/scheduletypes"; -import MobileReservationSheet from "../../Reservation/MobileReservationSheet"; -import DesktopReservationSheet from "../../Reservation/DesktopReservationSheet"; -import ScheduleSlot from "./ScheduleSlot"; +import { useSlotReservations } from "@/app/_hooks/useSlotReservations"; +import ReservationSheets from "../../Reservation/ReservationSheets"; import CurrentTimeIndicator from "./CurrentTimeIndicator"; +import ScheduleSlots from "./ScheduleSlots"; interface ScheduleRowProps { schedules: IReservation[]; @@ -22,11 +20,8 @@ interface ScheduleRowProps { export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const { schedules, room, slotHeight = 80, slotWidth = 72 } = props; - const user = useAuthStore((state) => state.user); - const [selectedTime, setSelectedTime] = useState(null); const [selectedSchedule, setSelectedSchedule] = useState(null); - const [selectedRoom, setSelectedRoom] = useState(room || null); const openSidebar = useSidebarStore((state) => state.openSidebar); const closeSidebar = useSidebarStore((state) => state.closeSidebar); @@ -34,81 +29,9 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { const startHour = 0; const endHour = 24; - const totalSlots = (endHour - startHour) * 2; // 30분 단위 const minutesPerSlot = 30; - const totalMinutes = (endHour - startHour) * 60; - - // timeToMinutes 함수 선언 - const timeToMinutes = (time: Date | string): number => { - const date = typeof time === "string" ? parseISO(time) : time; - const hours = date.getHours(); - const minutes = date.getMinutes(); - return hours * 60 + minutes; - }; - - // 슬롯별로 예약 상태를 저장하는 배열 생성 - const slotReservations = Array(totalSlots).fill(null) as (IReservation | null)[]; - - // 예약 정보를 슬롯 인덱스에 매핑 - schedules.forEach((schedule) => { - const startMinutes = timeToMinutes(schedule.startAt) - startHour * 60; - const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60; - - const startIndex = Math.floor(startMinutes / minutesPerSlot); - const endIndex = Math.ceil(endMinutes / minutesPerSlot); - for (let i = startIndex; i < endIndex; i++) { - slotReservations[i] = schedule; - } - }); - - // 슬롯 생성 - const slots = []; - for (let i = 0; i < totalSlots; i++) { - const schedule = slotReservations[i]; - if (schedule) { - // 예약된 슬롯의 시작 위치를 찾기 위해 이전 슬롯을 확인 - if (i === 0 || slotReservations[i - 1] !== schedule) { - // 예약된 슬롯의 시작 - const start = i; - let end = i; - // 예약된 슬롯의 끝 위치를 찾음 - while (end < totalSlots && slotReservations[end] === schedule) { - end++; - } - slots.push( - { - handleSlotClick(start, schedule); - }} - isReserved - schedule={schedule} - />, - ); - i = end - 1; // 다음 반복에서 end 위치부터 시작 - } - } else { - // 예약되지 않은 슬롯은 개별적으로 렌더링 - slots.push( - { - handleSlotClick(i); - }} - isReserved={false} - />, - ); - } - } + const slotReservations = useSlotReservations(schedules, startHour, endHour, minutesPerSlot); const handleSlotClick = (index: number, schedule?: IReservation): void => { const clickedTimeMinutes = startHour * 60 + index * minutesPerSlot; @@ -118,7 +41,6 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { setSelectedTime(timeString); setSelectedSchedule(schedule ?? null); - setSelectedRoom(room); openSidebar(); }; @@ -126,38 +48,30 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { closeSidebar(); setSelectedSchedule(null); setSelectedTime(null); - setSelectedRoom(null); }; return (
-
{slots}
+
+ +
- {selectedTime && selectedRoom ? ( - <> -
- -
-
- -
- - ) : null} +
); } From fdb399ae8be8e43558fbf28ca8257639405e5a79 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 17:48:29 +0900 Subject: [PATCH 58/72] =?UTF-8?q?Refactor:=20ReservationSheetContent=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/app/constants/reservationConstants.ts | 35 ++++++++++ .../Reservation/ReservationSheetContent.tsx | 66 ++++++++----------- .../Schedule/ScheduleRow/ScheduleSlot.tsx | 12 +--- 3 files changed, 67 insertions(+), 46 deletions(-) create mode 100644 apps/web/app/constants/reservationConstants.ts diff --git a/apps/web/app/constants/reservationConstants.ts b/apps/web/app/constants/reservationConstants.ts new file mode 100644 index 00000000..9813f9f3 --- /dev/null +++ b/apps/web/app/constants/reservationConstants.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +export enum ModalType { + CREATE_UPDATE = "create/update", + DELETE = "delete", +} + +export const NOTIFICATION_MESSAGES = { + create: "회의실이 예약되었습니다.", + update: "예약이 수정되었습니다.", + delete: "예약이 삭제되었습니다.", +}; + +export const QUERY_KEYS = { + meetings: (date: string, type: string) => ["meetings", date, type] as const, +}; + +export const MODAL_TEXT = { + titles: { + [ModalType.CREATE_UPDATE]: "회의실을 예약하시겠어요?", + [ModalType.DELETE]: "예약을 삭제하시겠어요?", + edit: "회의실 예약을 수정하시겠어요?", + }, + contents: { + [ModalType.CREATE_UPDATE]: "선택한 시간대의 회의실이 예약됩니다.", + edit: "선택한 시간대의 회의실 예약이 수정됩니다.", + [ModalType.DELETE]: "선택한 예약이 삭제됩니다.", + }, + confirmButtonNames: { + [ModalType.CREATE_UPDATE]: "예약하기", + edit: "수정하기", + [ModalType.DELETE]: "삭제하기", + }, + cancelButtonName: "취소하기", +}; diff --git a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx index d3d5026e..afb0e5ba 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx @@ -1,4 +1,9 @@ -import { useEffect, useState } from "react"; +/* eslint-disable @typescript-eslint/no-shadow */ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ + +"use client"; + +import { useState } from "react"; import { type IReservation } from "@repo/types"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { notify } from "@ui/index"; @@ -11,6 +16,9 @@ import { } from "@/api/reservations"; import { useAuthStore } from "@/src/stores/useAuthStore"; import { useDateStore } from "@/app/store/useDateStore"; +import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; +import { formatDate } from "@/app/utils/formatDate"; +import { MODAL_TEXT, ModalType, NOTIFICATION_MESSAGES, QUERY_KEYS } from "@/app/constants/reservationConstants"; import ReservationModal from "./ReservationModal"; import ReservationForm from "./ReservationForm"; @@ -25,17 +33,12 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr const { onClose, selectedTime, selectedSchedule, selectedRoom } = props; const queryClient = useQueryClient(); const [isModalOpen, setIsModalOpen] = useState(false); - const [modalType, setModalType] = useState<"create/update" | "delete">("create/update"); + const [modalType, setModalType] = useState(ModalType.CREATE_UPDATE); const user = useAuthStore((state) => state.user); const { selectedDate } = useDateStore(); - const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart( - 2, - "0", - )}-${String(selectedDate.day).padStart(2, "0")}`; - - const MeetingRoomsType = "room"; + const formattedDate = formatDate(selectedDate); const [formData, setFormData] = useState<{ data: CreateReservationRequest; @@ -43,25 +46,24 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr reservationId?: string; } | null>(null); - const isEditMode = Boolean(selectedSchedule) && selectedSchedule.user._id === user?._id; + const isEditMode = selectedSchedule && selectedSchedule.user._id === user?._id; const createOrUpdateReservation = useMutation({ mutationFn: (formData: { data: CreateReservationRequest; itemId: string; reservationId?: string }) => { if (isEditMode && formData.reservationId) { // 수정 모드일 경우 return updateReservation(formData.reservationId, formData.data); - } - // 생성 모드일 경우 - return createReservation(formData.itemId, formData.data); - + } + // 생성 모드일 경우 + return createReservation(formData.itemId, formData.data); }, onSuccess: async () => { notify({ type: "success", - message: isEditMode ? "예약이 수정되었습니다." : "회의실이 예약되었습니다.", + message: isEditMode ? NOTIFICATION_MESSAGES.update : NOTIFICATION_MESSAGES.create, }); await queryClient.invalidateQueries({ - queryKey: ["meetings", formattedDate, MeetingRoomsType], + queryKey: QUERY_KEYS.meetings(formattedDate, MEETING_ROOMS_TYPE), }); setIsModalOpen(false); onClose(); @@ -73,10 +75,10 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr onSuccess: async () => { notify({ type: "success", - message: "예약이 삭제되었습니다.", + message: NOTIFICATION_MESSAGES.delete, }); await queryClient.invalidateQueries({ - queryKey: ["meetings", formattedDate, MeetingRoomsType], + queryKey: QUERY_KEYS.meetings(formattedDate, MEETING_ROOMS_TYPE), }); setIsModalOpen(false); onClose(); @@ -85,12 +87,12 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr const handleSubmit = (data: CreateReservationRequest, itemId: string, reservationId?: string): void => { setFormData({ data, itemId, reservationId }); - setModalType("create/update"); // 모달 타입 설정 + setModalType(isEditMode ? ModalType.CREATE_UPDATE : ModalType.CREATE_UPDATE); // 동일하게 설정, 추후 수정 가능 setIsModalOpen(true); }; const handleDelete = (): void => { - setModalType("delete"); // 모달 타입 설정 + setModalType(ModalType.DELETE); setIsModalOpen(true); }; @@ -100,23 +102,13 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr } }; - const modalTitle = - modalType === "delete" - ? "예약을 삭제하시겠어요?" - : isEditMode - ? "회의실 예약을 수정하시겠어요?" - : "회의실을 예약하시겠어요?"; + const modalTitle = isEditMode ? MODAL_TEXT.titles.edit : MODAL_TEXT.titles[modalType]; - const modalContent = - modalType === "delete" ? ( - <>선택한 예약이 삭제됩니다. - ) : isEditMode ? ( - <>선택한 시간대의 회의실 예약이 수정됩니다. - ) : ( - <>선택한 시간대의 회의실이 예약됩니다. - ); + const modalContent = isEditMode ? MODAL_TEXT.contents.edit : MODAL_TEXT.contents[modalType]; - const modalConfirmButtonName = modalType === "delete" ? "삭제하기" : isEditMode ? "수정하기" : "예약하기"; + const modalConfirmButtonName = isEditMode + ? MODAL_TEXT.confirmButtonNames.edit + : MODAL_TEXT.confirmButtonNames[modalType]; return ( <> @@ -133,11 +125,11 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr setIsModalOpen(false); }} onConfirm={() => { - if (modalType === "create/update") { + if (modalType === ModalType.CREATE_UPDATE) { if (formData) { createOrUpdateReservation.mutate(formData); } - } else if (modalType === "delete") { + } else if (modalType === ModalType.DELETE) { confirmDelete(); } setIsModalOpen(false); @@ -145,7 +137,7 @@ export default function ReservationSheetContent(props: ReservationSheetContentPr }} title={modalTitle} content={modalContent} - cancelButtonName="취소하기" + cancelButtonName={MODAL_TEXT.cancelButtonName} confirmButtonName={modalConfirmButtonName} /> diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx index 34ae3cb1..f9ef3f79 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/ScheduleSlot.tsx @@ -1,9 +1,8 @@ -// ScheduleSlot.tsx - +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { useEffect, useState } from "react"; import { type IReservation } from "@repo/types"; import { useSidebarStore } from "@/app/store/useSidebarStore"; -import { useAuthStore } from "@/src/stores/useAuthStore"; // 현재 사용자 정보를 가져오기 위해 추가 +import { useAuthStore } from "@/src/stores/useAuthStore"; interface ScheduleSlotProps { index: number; @@ -21,21 +20,19 @@ export default function ScheduleSlot(props: ScheduleSlotProps): JSX.Element { const { isSidebarOpen } = useSidebarStore(); const [isClicked, setIsClicked] = useState(false); - const user = useAuthStore((state) => state.user); // 현재 사용자 정보 가져오기 + const user = useAuthStore((state) => state.user); const handleClick = (): void => { setIsClicked(true); onClick(); }; - // Sidebar가 닫힐 때 클릭 상태 초기화 useEffect(() => { if (!isSidebarOpen) { setIsClicked(false); } }, [isSidebarOpen]); - // 예약된 슬롯이 현재 사용자의 예약인지 확인 const isUserReservation = isReserved && schedule && schedule.user._id === user?._id; return ( @@ -48,21 +45,18 @@ export default function ScheduleSlot(props: ScheduleSlotProps): JSX.Element { role="button" tabIndex={0} > - {/* 예약된 슬롯의 경우 중간에 가로 막대를 표시 */} {isReserved && schedule ? (
) : null} - {/* 좌측 테두리 */} {index % 2 === 0 ? (
) : (
)} - {/* 하단 테두리 */}
); From 66e93ae854ad1bda9c7e334130398ba86365681e Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 18:27:10 +0900 Subject: [PATCH 59/72] =?UTF-8?q?Feat:=20reservationForm=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReservationForm/AttendeesMultiSelect.tsx | 82 +++++++++++++++++++ .../ReservationForm/DeleteButton.tsx | 11 +++ .../ReservationForm/NotesInput.tsx | 22 +++++ .../ReservationForm/RoomDropdown.tsx | 44 ++++++++++ .../ReservationForm/TimeSelectors.tsx | 74 +++++++++++++++++ 5 files changed, 233 insertions(+) create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx create mode 100644 apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx new file mode 100644 index 00000000..bad2a07c --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx @@ -0,0 +1,82 @@ +import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; +import { Controller } from "react-hook-form"; +import { Badge } from "@ui/index"; +import { type IUser } from "@repo/types"; +import Profile from "@/components/common/Profile"; +import { ERROR_MESSAGES, FORM_LABELS } from "@/app/constants/reservationFormConstants"; +import { type AttendeesMultiSelectProps } from "@/app/types/ReservationFormTypes"; + +export function AttendeesMultiSelect({ + control, + allUsersData, + isLoading, + isError, +}: AttendeesMultiSelectProps): JSX.Element { + return ( + ( + { + field.onChange(value); + }} + > + + {field.value.length > 0 ? ( +
+ {field.value.slice(0, 3).map((name) => { + const user = allUsersData.find((user) => user.name === name); + return user ? ( + + {user.name} + + ) : null; + })} + {field.value.length > 3 && ( + +{field.value.length - 3}명 + )} +
+ ) : ( + FORM_LABELS.selectAttendees + )} +
+ + {isLoading ? ( +
사용자 데이터 로딩 중...
+ ) : isError ? ( +
{ERROR_MESSAGES.userFetchError}
+ ) : allUsersData.length === 0 ? ( +
참여자가 없습니다.
+ ) : ( + allUsersData.map((user: IUser) => ( + +
+ +
+ {user.teams.map((team: string, index: number) => ( +
+ {team} +
+ ))} +
+
+
+ )) + )} +
+
+ )} + /> + ); +} diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx new file mode 100644 index 00000000..f22d1df4 --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/DeleteButton.tsx @@ -0,0 +1,11 @@ +import Button from "@ui/src/components/common/Button"; +import { BUTTON_TEXT } from "@/app/constants/reservationFormConstants"; +import { type DeleteButtonProps } from "@/app/types/ReservationFormTypes"; + +export function DeleteButton({ onDelete }: DeleteButtonProps): JSX.Element { + return ( + + ); +} diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx new file mode 100644 index 00000000..ed3f5309 --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/NotesInput.tsx @@ -0,0 +1,22 @@ +import { Controller } from "react-hook-form"; +import Input from "@ui/src/components/common/Input"; +import { ERROR_MESSAGES, FORM_LABELS } from "@/app/constants/reservationFormConstants"; +import { type NotesInputProps } from "@/app/types/ReservationFormTypes"; + +export function NotesInput({ control }: NotesInputProps): JSX.Element { + return ( + ( + + )} + /> + ); +} diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx new file mode 100644 index 00000000..223ce15a --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx @@ -0,0 +1,44 @@ +import Dropdown from "@ui/src/components/common/Dropdown"; +import { useQuery } from "@tanstack/react-query"; +import { type TBaseItem } from "@repo/types"; +import { getAllItems } from "@/api/items"; +import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; +import { ERROR_MESSAGES, FORM_LABELS } from "@/app/constants/reservationFormConstants"; +import { type RoomDropdownProps } from "@/app/types/ReservationFormTypes"; + +export function RoomDropdown({ selectedRoom, onSelect }: RoomDropdownProps): JSX.Element { + const { + data: roomsData = [], + isLoading, + isError, + } = useQuery({ + queryKey: ["Rooms", MEETING_ROOMS_TYPE], + queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), + }); + + return ( + { + if (typeof value === "string") { + onSelect(value); + } + }} + > + {selectedRoom || FORM_LABELS.selectRoom} + + {isLoading ? ( +
회의실 로딩 중...
+ ) : isError ? ( +
{ERROR_MESSAGES.roomFetchError}
+ ) : ( + roomsData.map((room) => ( + + {room.name} + + )) + )} +
+
+ ); +} diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx new file mode 100644 index 00000000..cbba53b7 --- /dev/null +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx @@ -0,0 +1,74 @@ +import Dropdown from "@ui/src/components/common/Dropdown"; +import { Controller } from "react-hook-form"; +import { timeOptions } from "@/app/constants/timeOptions"; +import { FORM_LABELS, ERROR_MESSAGES } from "@/app/constants/reservationFormConstants"; +import { type TimeSelectorsProps } from "@/app/types/ReservationFormTypes"; + +export function TimeSelectors({ control, errors, trigger, clearErrors }: TimeSelectorsProps): JSX.Element { + return ( +
+
+ ( + { + field.onChange(value); + clearErrors("endAt"); + trigger("endAt"); + }} + isError={Boolean(error)} + errorMessage={error?.message} + > + + {field.value || FORM_LABELS.selectStartTime} + + + {timeOptions.map((time) => ( + + {time} + + ))} + + + )} + /> +
+ +
+ true, // 검증 로직을 메인 컴포넌트에서 처리 + }} + render={({ field, fieldState: { error } }) => ( + { + field.onChange(value); + }} + isError={Boolean(error)} + errorMessage={error?.message} + > + + {field.value || FORM_LABELS.selectEndTime} + + + {timeOptions.map((time) => ( + + {time} + + ))} + + + )} + /> +
+
+ ); +} From 46827bc3f5d72ebb5d5550b180541af38504637d Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 18:28:22 +0900 Subject: [PATCH 60/72] =?UTF-8?q?Feat:=20Form=20Type,=20constants,=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/constants/reservationFormConstants.ts | 34 +++++++ apps/web/app/types/ReservationFormTypes.ts | 40 +++++++++ apps/web/app/utils/validateTime.ts | 90 +++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 apps/web/app/constants/reservationFormConstants.ts create mode 100644 apps/web/app/types/ReservationFormTypes.ts create mode 100644 apps/web/app/utils/validateTime.ts diff --git a/apps/web/app/constants/reservationFormConstants.ts b/apps/web/app/constants/reservationFormConstants.ts new file mode 100644 index 00000000..e0293c49 --- /dev/null +++ b/apps/web/app/constants/reservationFormConstants.ts @@ -0,0 +1,34 @@ +export const FORM_LABELS = { + meetingTitle: "미팅 제목", + meetingTitlePlaceholder: "미팅 제목을 입력해주세요.", + selectRoom: "회의실 선택", + selectStartTime: "시작 시간", + selectEndTime: "종료 시간", + selectAttendees: "참여자 선택", +}; + +export const BUTTON_TEXT = { + delete: "삭제하기", + update: "수정하기", + create: "예약하기", + cancel: "취소하기", +}; + +export const ERROR_MESSAGES = { + meetingTitleRequired: "미팅 제목을 입력해주세요.", + timeRequired: "시작 시간과 종료 시간을 모두 선택해주세요.", + endTimeMinimum: "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다.", + timeOverlap: "선택한 시간에 이미 예약이 있습니다.", + userFetchError: "사용자 데이터를 불러오는 데 실패했습니다.", + roomFetchError: "회의실 정보를 불러오는 데 실패했습니다.", +}; + +export const QUERY_KEYS = { + meetings: (date: string, type: string) => ["meetings", date, type] as const, + rooms: "Rooms", + allUsers: "AllUsers", +}; + +export const TIME_INTERVAL = { + minimumDifference: 30, +}; diff --git a/apps/web/app/types/ReservationFormTypes.ts b/apps/web/app/types/ReservationFormTypes.ts new file mode 100644 index 00000000..702f55a9 --- /dev/null +++ b/apps/web/app/types/ReservationFormTypes.ts @@ -0,0 +1,40 @@ +import { type Control, type FieldErrors, type UseFormTrigger, type UseFormClearErrors } from "react-hook-form"; +import { type IReservation, type IUser } from "@repo/types"; +import { type CreateReservationRequest } from "@/api/reservations"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; + +export interface ReservationFormProps { + onSubmit: (data: CreateReservationRequest, itemId: string, reservationId?: string) => void; + selectedTime: string; + selectedSchedule?: IReservation | null; + resetTrigger?: number; + selectedRoom?: SelectedRoom | null; + onDelete: () => void; +} + +export interface NotesInputProps { + control: Control; +} + +export interface RoomDropdownProps { + selectedRoom: string; + onSelect: (value: string) => void; +} + +export interface TimeSelectorsProps { + control: Control; + errors: FieldErrors; + trigger: UseFormTrigger; + clearErrors: UseFormClearErrors; +} + +export interface AttendeesMultiSelectProps { + control: Control; + allUsersData: IUser[]; + isLoading: boolean; + isError: boolean; +} + +export interface DeleteButtonProps { + onDelete: () => void; +} diff --git a/apps/web/app/utils/validateTime.ts b/apps/web/app/utils/validateTime.ts new file mode 100644 index 00000000..a4527ba2 --- /dev/null +++ b/apps/web/app/utils/validateTime.ts @@ -0,0 +1,90 @@ +import { parse, differenceInMinutes } from "date-fns"; +import { type IReservation } from "@repo/types"; +import { type UseFormGetValues } from "react-hook-form"; +import { ERROR_MESSAGES, TIME_INTERVAL } from "@/app/constants/reservationFormConstants"; +import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; +import { type CreateReservationRequest } from "@/api/reservations"; + +interface ValidateTimeParams { + endAt: string; + getValues: UseFormGetValues; + selectedMeetingRoom: SelectedRoom | null | undefined; + selectedDate: { + year: number; + month: number; + day: number; + }; + meetingsData: IReservation[]; + selectedReservationId?: string; +} + +export function validateEndAt({ + endAt, + getValues, + selectedMeetingRoom, + selectedDate, + meetingsData, + selectedReservationId, +}: ValidateTimeParams): boolean | string { + const startAtValue = getValues("startAt"); + + if (!startAtValue || !endAt) { + return ERROR_MESSAGES.timeRequired; + } + + const start = parse(startAtValue, "HH:mm", new Date()); + const end = parse(endAt, "HH:mm", new Date()); + + const diff = differenceInMinutes(end, start); + + if (diff < TIME_INTERVAL.minimumDifference) { + return ERROR_MESSAGES.endTimeMinimum; + } + + if (!selectedMeetingRoom?._id) { + return true; + } + + const { year, month, day } = selectedDate; + + const newStart = new Date( + `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${startAtValue}:00`, + ); + const newEnd = new Date(`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${endAt}:00`); + + const isOverlap = meetingsData.some((reservation) => { + if (reservation.itemType !== MEETING_ROOMS_TYPE) { + return false; + } + + let itemId: string; + if (typeof reservation.item === "string") { + itemId = reservation.item; + } else if (reservation.item && "_id" in reservation.item) { + itemId = reservation.item._id; + } else { + return false; + } + + if (itemId !== selectedMeetingRoom._id) { + return false; + } + + // 수정된 부분: selectedReservationId를 사용하여 현재 예약을 제외 + if (selectedReservationId === reservation._id) { + return false; + } + + const existingStart = new Date(reservation.startAt); + const existingEnd = new Date(reservation.endAt); + + return newStart < existingEnd && newEnd > existingStart; + }); + + if (isOverlap) { + return ERROR_MESSAGES.timeOverlap; + } + + return true; +} From 40141c47f57446266715a5c65dcdeb963134151c Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 18:29:53 +0900 Subject: [PATCH 61/72] =?UTF-8?q?Refactor:=20Form,=20sheet=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 418 ++++-------------- .../Reservation/ReservationSheetContent.tsx | 2 +- 2 files changed, 76 insertions(+), 344 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 8838d2a8..0e3fd2a8 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -1,65 +1,55 @@ +// components/ReservationForm/ReservationForm.tsx + "use client"; -import Input from "@ui/src/components/common/Input"; -import Dropdown from "@ui/src/components/common/Dropdown"; -import { useForm, Controller } from "react-hook-form"; -import Button from "@ui/src/components/common/Button"; -import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; -import { useEffect, useState } from "react"; -import { type TBaseItem, type IReservation, type IUser } from "@repo/types"; -import { format, parse, differenceInMinutes, addMinutes } from "date-fns"; +import React, { useEffect, useState } from "react"; +import { useForm, type SubmitHandler } from "react-hook-form"; import { useQuery } from "@tanstack/react-query"; -import { Badge } from "@ui/index"; -import { timeOptions } from "@/app/constants/timeOptions"; -import Profile from "@/components/common/Profile"; -import { type SelectedRoom } from "@/app/types/scheduletypes"; +import { format, parse, differenceInMinutes, addMinutes } from "date-fns"; +import { type TBaseItem, type IUser, type IReservation } from "@repo/types"; +import { Button } from "@ui/index"; +import { useAuthStore } from "@/src/stores/useAuthStore"; +import { useDateStore } from "@/app/store/useDateStore"; import { getAllUser } from "@/api/users"; import { getAllItems } from "@/api/items"; -import { useAuthStore } from "@/src/stores/useAuthStore"; import { getReservationsByTypeAndDate, type CreateReservationRequest } from "@/api/reservations"; -import { useDateStore } from "@/app/store/useDateStore"; - -interface ReservationFormProps { - onSubmit: (data: CreateReservationRequest, itemId: string, reservationId?: string) => void; - selectedTime: string; - selectedSchedule?: IReservation | null; - resetTrigger?: number; - selectedRoom?: SelectedRoom | null; - onDelete: () => void; -} - -export default function ReservationForm(props: ReservationFormProps): JSX.Element { - const { onSubmit, selectedTime, resetTrigger, selectedRoom, selectedSchedule, onDelete } = props; - - const MeetingRoomsType = "room"; - - // 현재 사용자 데이터 가져오기 +import { BUTTON_TEXT, ERROR_MESSAGES } from "@/app/constants/reservationFormConstants"; +import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; +import { formatDate } from "@/app/utils/formatDate"; +import { type SelectedRoom } from "@/app/types/scheduletypes"; +import { type ReservationFormProps } from "@/app/types/ReservationFormTypes"; +import { validateEndAt } from "@/app/utils/validateTime"; +import { AttendeesMultiSelect } from "./ReservationForm/AttendeesMultiSelect"; +import { DeleteButton } from "./ReservationForm/DeleteButton"; +import { NotesInput } from "./ReservationForm/NotesInput"; +import { RoomDropdown } from "./ReservationForm/RoomDropdown"; +import { TimeSelectors } from "./ReservationForm/TimeSelectors"; + +export function ReservationForm({ + onSubmit, + selectedTime, + resetTrigger, + selectedRoom, + selectedSchedule, + onDelete, +}: ReservationFormProps): JSX.Element { const user = useAuthStore((state) => state.user); - const isEditMode = Boolean(selectedSchedule) && selectedSchedule.user._id === user?._id; + const isEditMode = selectedSchedule && selectedSchedule.user._id === user?._id; const { selectedDate } = useDateStore(); - const formattedDate = `${String(selectedDate.year)}-${String(selectedDate.month).padStart( - 2, - "0", - )}-${String(selectedDate.day).padStart(2, "0")}`; + const formattedDate = formatDate(selectedDate); - const { data: meetingsData = [], isLoading: meetingsIsLoading } = useQuery({ - queryKey: ["meetings", formattedDate, MeetingRoomsType], - queryFn: () => getReservationsByTypeAndDate({ itemType: MeetingRoomsType, date: formattedDate }), + const { data: meetingsData = [] } = useQuery({ + queryKey: ["meetings", formattedDate, MEETING_ROOMS_TYPE], + queryFn: () => getReservationsByTypeAndDate({ itemType: MEETING_ROOMS_TYPE, date: formattedDate }), }); - // 방 데이터 가져오기 - const { - data: roomsData = [], - isLoading: roomsIsLoading, - isError: roomsIsError, - } = useQuery({ - queryKey: ["Rooms", MeetingRoomsType], - queryFn: () => getAllItems({ itemType: MeetingRoomsType }), + const { data: roomsData = [] } = useQuery({ + queryKey: ["Rooms", MEETING_ROOMS_TYPE], + queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), }); - // 전체 사용자 데이터 가져오기 const { data: allUsersData = [], isLoading: allUsersIsLoading, @@ -69,14 +59,12 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen queryFn: getAllUser, }); - // 시작 시간을 기반으로 종료 시간을 설정하는 함수 const getDefaultEndAt = (startAt: string): string => { const start = parse(startAt, "HH:mm", new Date()); const end = addMinutes(start, 30); return format(end, "HH:mm"); }; - // 폼의 기본 값을 설정합니다. const defaultValues: CreateReservationRequest = { userId: user!._id, itemType: "room", @@ -103,88 +91,23 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen mode: "onChange", }); - const validateEndAt = (endAt: string): boolean | string => { - const startAtValue = getValues("startAt"); - - if (!startAtValue || !endAt) { - return "시작 시간과 종료 시간을 모두 선택해주세요."; - } - - // 시작 시간과 종료 시간을 파싱합니다. - const start = parse(startAtValue, "HH:mm", new Date()); - const end = parse(endAt, "HH:mm", new Date()); - - // 시간 차이를 계산합니다. - const diff = differenceInMinutes(end, start); - - if (diff < 30) { - return "종료 시간은 시작 시간보다 최소 30분 이후여야 합니다."; - } - - // 추가: 시간 겹침 여부 확인 - if (!selectedMeetingRoom?._id) { - return true; // 회의실이 선택되지 않은 경우 검증 통과 - } - - const { year, month, day } = selectedDate; - - const newStart = new Date( - `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${startAtValue}:00`, - ); - const newEnd = new Date(`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${endAt}:00`); - - const isOverlap = meetingsData.some((reservation) => { - // 예약의 itemType이 "room"인지 확인합니다. - if (reservation.itemType !== "room") { - return false; - } - - // 예약의 item에서 itemId를 추출합니다. - let itemId: string; - if (typeof reservation.item === "string") { - itemId = reservation.item; - } else if (reservation.item && "_id" in reservation.item) { - itemId = reservation.item._id; - } else { - return false; // itemId를 추출할 수 없으면 건너뜁니다. - } - - // 현재 선택된 회의실과 동일한지 확인합니다. - if (itemId !== selectedMeetingRoom._id) { - return false; - } - - // 현재 수정 중인 예약은 제외합니다. - if (selectedSchedule && reservation._id === selectedSchedule._id) { - return false; - } - - // 기존 예약의 시작 시간과 종료 시간을 Date 객체로 변환합니다. - const existingStart = new Date(reservation.startAt); - const existingEnd = new Date(reservation.endAt); - - // 시간 겹침 여부를 확인합니다. - return newStart < existingEnd && newEnd > existingStart; + const validateEndAtFunction = (endAt: string): boolean | string => { + return validateEndAt({ + endAt, + getValues, + selectedMeetingRoom, + selectedDate, + meetingsData, + selectedReservationId: selectedSchedule?._id, // 추가된 부분 }); - - if (isOverlap) { - return "선택한 시간에 이미 예약이 있습니다."; - } - - return true; }; - // 상태를 추가하여 제출된 데이터를 저장 - const [submittedData, setSubmittedData] = useState(null); - - // ReservationForm 컴포넌트 내 const [selectedMeetingRoom, setSelectedMeetingRoom] = useState(selectedRoom); const attendeesSelected = watch("attendees").length > 0; const startAtValue = watch("startAt"); const endAtValue = watch("endAt"); - // resetTrigger 또는 selectedSchedule이 변경될 때마다 폼을 리셋 useEffect(() => { if (allUsersData.length > 0) { const newEndAt = selectedSchedule @@ -203,13 +126,11 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen } }, [resetTrigger, reset, selectedTime, selectedRoom, selectedSchedule, allUsersData]); - // startAt 값이 변경될 때 endAt을 자동으로 30분 후로 설정 useEffect(() => { if (startAtValue) { const currentEndAt = parse(endAtValue, "HH:mm", new Date()); const calculatedEndAt = addMinutes(parse(startAtValue, "HH:mm", new Date()), 30); - // 종료 시간이 현재 설정된 종료 시간보다 작을 경우, 종료 시간을 30분 후로 설정 if (differenceInMinutes(calculatedEndAt, currentEndAt) > 0) { const newEndAt = format(calculatedEndAt, "HH:mm"); setValue("endAt", newEndAt); @@ -217,84 +138,69 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen } }, [startAtValue, endAtValue, setValue]); - // onFormSubmit 함수 수정 - const onFormSubmit = (data: CreateReservationRequest): void => { + const onFormSubmit: SubmitHandler = (data) => { if (!user || !selectedMeetingRoom?._id) { return; } const { year, month, day } = selectedDate; - // 새로운 예약의 시작 시간과 종료 시간을 Date 객체로 변환합니다. const newStart = new Date( `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.startAt}:00`, ); + const newEnd = new Date( `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${data.endAt}:00`, ); - // 추가적인 검증: 종료 시간이 시작 시간 이후인지 확인합니다. if (newEnd <= newStart) { setError("endAt", { type: "manual", - message: "종료 시간은 시작 시간 이후여야 합니다.", + message: ERROR_MESSAGES.endTimeMinimum, }); return; } - // 시간 겹침 여부를 확인하는 함수 - function isTimeOverlap(newStart: Date, newEnd: Date, existingStart: Date, existingEnd: Date): boolean { - return newStart < existingEnd && newEnd > existingStart; - } - - // 기존 예약과 시간 겹침 여부를 확인합니다. const isOverlap = meetingsData.some((reservation) => { - // 예약의 itemType이 "room"인지 확인합니다. - if (reservation.itemType !== "room") { + if (reservation.itemType !== MEETING_ROOMS_TYPE) { return false; } - // 예약의 item에서 itemId를 추출합니다. let itemId: string; if (typeof reservation.item === "string") { itemId = reservation.item; } else if (reservation.item && "_id" in reservation.item) { itemId = reservation.item._id; } else { - return false; // itemId를 추출할 수 없으면 건너뜁니다. + return false; } - // 현재 선택된 회의실과 동일한지 확인합니다. if (itemId !== selectedMeetingRoom._id) { return false; } - // 현재 수정 중인 예약은 제외합니다. if (selectedSchedule && reservation._id === selectedSchedule._id) { return false; } - // 기존 예약의 시작 시간과 종료 시간을 Date 객체로 변환합니다. const existingStart = new Date(reservation.startAt); const existingEnd = new Date(reservation.endAt); - // 시간 겹침 여부를 확인합니다. - return isTimeOverlap(newStart, newEnd, existingStart, existingEnd); + return newStart < existingEnd && newEnd > existingStart; }); if (isOverlap) { setError("startAt", { type: "manual", - message: "선택한 시간에 이미 예약이 있습니다.", + message: ERROR_MESSAGES.timeOverlap, }); setError("endAt", { type: "manual", - message: "선택한 시간에 이미 예약이 있습니다.", + message: ERROR_MESSAGES.timeOverlap, }); return; } - // 참석자 ID 배열 생성 const attendeeIds = data.attendees .map((name: string) => { const selectedUser = allUsersData.find((user) => user.name === name); @@ -315,217 +221,43 @@ export default function ReservationForm(props: ReservationFormProps): JSX.Elemen attendees: attendeeIds, }; - // 상태에 저장하여 화면에 표시 - setSubmittedData(mappedData); - onSubmit(mappedData, selectedMeetingRoom._id, selectedSchedule?._id); }; return (
- {/* 미팅 제목 입력 */} - } - /> - - {/* 미팅룸 선택 */} - { - if (typeof value === "string") { - const room = roomsData.find((room) => room.name === value); - if (room) { - setSelectedMeetingRoom({ _id: room._id, name: room.name }); - // Trigger validation of endAt when meeting room changes - trigger("endAt"); - } + + + { + const room = roomsData.find((room) => room.name === value); + if (room) { + setSelectedMeetingRoom({ _id: room._id, name: room.name }); + trigger("endAt"); } }} - > - {selectedMeetingRoom?.name || "회의실 선택"} - - {roomsIsLoading ? ( -
회의실 로딩 중...
- ) : roomsIsError ? ( -
회의실 정보를 불러오는 데 실패했습니다.
- ) : ( - roomsData.map((room) => ( - - {room.name} - - )) - )} -
-
- - {/* 시작 시간 및 종료 시간 선택 */} -
- {/* 시작 시간 */} -
- ( - { - field.onChange(value); - // Clear endAt error when startAt changes - clearErrors("endAt"); - // Trigger validation of endAt - trigger("endAt"); - }} - isError={Boolean(errors.startAt)} - errorMessage={errors.startAt?.message ?? ""} - > - {field.value || selectedTime} - - {timeOptions.map((time) => ( - - {time} - - ))} - - - )} - /> -
- - {/* 종료 시간 */} -
- ( - { - field.onChange(value); - }} - isError={Boolean(errors.endAt)} - errorMessage={errors.endAt?.message ?? ""} - > - {field.value || "종료 시간 선택"} - - {timeOptions.map((time) => ( - - {time} - - ))} - - - )} - /> -
-
- - {/* 참여자 선택 */} - + + + + ( - { - field.onChange(value); - }} - > - - {field.value.length > 0 ? ( -
- {field.value.slice(0, 3).map((name) => { - const user = allUsersData.find((user) => user.name === name); - return user ? ( - - {user.name} - - ) : null; - })} - {field.value.length > 3 && ( - +{field.value.length - 3}명 - )} -
- ) : ( - "참여자 선택" - )} -
- - {allUsersIsLoading ? ( -
사용자 데이터 로딩 중...
- ) : allUsersIsError ? ( -
사용자 데이터를 불러오는 데 실패했습니다.
- ) : allUsersData.length === 0 ? ( -
참여자가 없습니다.
- ) : ( - allUsersData.map((user) => ( - -
- -
- {user.teams.map((team, index) => ( -
- {team} -
- ))} -
-
-
- )) - )} -
-
- )} + allUsersData={allUsersData} + isLoading={allUsersIsLoading} + isError={allUsersIsError} /> - {/* 삭제하기 버튼 (수정 모드일 때만 표시) */} - {isEditMode ? : null} - {/* 예약하기 버튼 */} + + {isEditMode ? : null} + - - {/* 제출된 데이터 표시 */} - {submittedData ? ( -
-

제출된 데이터

-
-            {JSON.stringify(submittedData, null, 2)}
-            
- 회의실: {selectedMeetingRoom?._id || "선택된 회의실 없음"} - {selectedTime} -
-
-
- ) : null}
); } diff --git a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx index afb0e5ba..07500e6d 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationSheetContent.tsx @@ -20,7 +20,7 @@ import { MEETING_ROOMS_TYPE } from "@/app/constants/meetingRoomsType"; import { formatDate } from "@/app/utils/formatDate"; import { MODAL_TEXT, ModalType, NOTIFICATION_MESSAGES, QUERY_KEYS } from "@/app/constants/reservationConstants"; import ReservationModal from "./ReservationModal"; -import ReservationForm from "./ReservationForm"; +import { ReservationForm } from "./ReservationForm"; interface ReservationSheetContentProps { onClose: () => void; From 6cd3733dbe1e380df73798de72797d42da5ab094 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 19:21:09 +0900 Subject: [PATCH 62/72] =?UTF-8?q?Fix:=20=EC=8B=9C=EA=B0=84=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=20=EC=97=90=EB=9F=AC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?getRoomSchedule=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleTable/ScheduleTableDesktop.tsx | 13 +++++++++++-- .../ScheduleTable/ScheduleTableMobile.tsx | 14 +++++++++++--- apps/web/app/utils/getRoomSchedules.ts | 15 --------------- 3 files changed, 22 insertions(+), 20 deletions(-) delete mode 100644 apps/web/app/utils/getRoomSchedules.ts diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index 45bc9810..4f62a454 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -1,7 +1,7 @@ "use client"; import { type TBaseItem, type IReservation } from "@repo/types"; -import { getRoomSchedules } from "@/app/utils/getRoomSchedules"; + import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import CurrentTimeIndicator from "../ScheduleRow/CurrentTimeIndicator"; @@ -29,7 +29,16 @@ export default function ScheduleTableDesktop(props: ScheduleTableDesktopProps):
{rooms.map((room) => { - const roomSchedules = getRoomSchedules(room, meetingsData, selectedDate); + const roomSchedules = meetingsData.filter((schedule) => { + const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; + + const isSameRoom = scheduleItemId === room._id; + + const scheduleDate = new Date(schedule.startAt).toLocaleDateString("en-CA", { timeZone: "Asia/Seoul" }); + const isSameDate = scheduleDate === selectedDate; + + return isSameRoom && isSameDate; + }); return (
diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx index 5d1473cd..a15d9403 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx @@ -2,7 +2,6 @@ import { type TBaseItem } from "@repo/types"; import { type IReservation } from "@repo/types/src/reservationType"; -import { getRoomSchedules } from "@/app/utils/getRoomSchedules"; import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import TimeText from "../ScheduleRow/TimeText"; @@ -19,7 +18,16 @@ export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JS return (
{rooms.map((room) => { - const roomSchedules = getRoomSchedules(room, meetingsData, selectedDate); + const roomSchedules = meetingsData.filter((schedule) => { + const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; + + const isSameRoom = scheduleItemId === room._id; + + const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; + const isSameDate = scheduleDate === selectedDate; + + return isSameRoom && isSameDate; + }); return (
@@ -29,7 +37,7 @@ export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JS
diff --git a/apps/web/app/utils/getRoomSchedules.ts b/apps/web/app/utils/getRoomSchedules.ts deleted file mode 100644 index d99a12e7..00000000 --- a/apps/web/app/utils/getRoomSchedules.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type IReservation } from "@repo/types/src/reservationType"; -import { type TBaseItem } from "@repo/types"; - -export function getRoomSchedules(room: TBaseItem, meetingsData: IReservation[], selectedDate: string): IReservation[] { - return meetingsData.filter((schedule) => { - const scheduleItemId = typeof schedule.item === "string" ? schedule.item : schedule.item._id; - - const isSameRoom = scheduleItemId === room._id; - - const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; - const isSameDate = scheduleDate === selectedDate; - - return isSameRoom && isSameDate; - }); -} From 607ef16e76e3ee97a3ba53e7db371ab2e36ec05b Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 20:44:19 +0900 Subject: [PATCH 63/72] =?UTF-8?q?Fix:=20eslint=20error=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm.tsx | 12 +- .../ReservationForm/AttendeesMultiSelect.tsx | 26 ++- .../ReservationForm/RoomDropdown.tsx | 1 + .../ReservationForm/TimeSelectors.tsx | 149 ++++++++++-------- .../ScheduleTable/ScheduleTableDesktop.tsx | 1 - apps/web/app/types/ReservationFormTypes.ts | 1 + apps/web/app/utils/validateTime.ts | 3 +- 7 files changed, 111 insertions(+), 82 deletions(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx index 0e3fd2a8..db6af654 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm.tsx @@ -1,8 +1,8 @@ -// components/ReservationForm/ReservationForm.tsx +/* eslint-disable */ "use client"; -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { useForm, type SubmitHandler } from "react-hook-form"; import { useQuery } from "@tanstack/react-query"; import { format, parse, differenceInMinutes, addMinutes } from "date-fns"; @@ -239,7 +239,13 @@ export function ReservationForm({ }} /> - + 0 ? (
{field.value.slice(0, 3).map((name) => { - const user = allUsersData.find((user) => user.name === name); - return user ? ( - - {user.name} + const matchingUser = allUsersData.find((user) => user.name === name); + return matchingUser ? ( + + {matchingUser.name} ) : null; })} @@ -43,13 +43,12 @@ export function AttendeesMultiSelect({ )} - {isLoading ? ( -
사용자 데이터 로딩 중...
- ) : isError ? ( -
{ERROR_MESSAGES.userFetchError}
- ) : allUsersData.length === 0 ? ( -
참여자가 없습니다.
- ) : ( + {isLoading ?
사용자 데이터 로딩 중...
: null} + {isError && !isLoading ?
{ERROR_MESSAGES.userFetchError}
: null} + {!isLoading && !isError && allUsersData.length === 0 &&
참여자가 없습니다.
} + {!isLoading && + !isError && + allUsersData.length > 0 && allUsersData.map((user: IUser) => (
@@ -61,7 +60,7 @@ export function AttendeesMultiSelect({ className="min-w-140" />
- {user.teams.map((team: string, index: number) => ( + {user.teams.map((team: string) => (
- )) - )} + ))} )} diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx index 223ce15a..bfc5ae39 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/RoomDropdown.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ import Dropdown from "@ui/src/components/common/Dropdown"; import { useQuery } from "@tanstack/react-query"; import { type TBaseItem } from "@repo/types"; diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx index cbba53b7..5068ce69 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/TimeSelectors.tsx @@ -1,74 +1,99 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ + import Dropdown from "@ui/src/components/common/Dropdown"; import { Controller } from "react-hook-form"; import { timeOptions } from "@/app/constants/timeOptions"; import { FORM_LABELS, ERROR_MESSAGES } from "@/app/constants/reservationFormConstants"; import { type TimeSelectorsProps } from "@/app/types/ReservationFormTypes"; -export function TimeSelectors({ control, errors, trigger, clearErrors }: TimeSelectorsProps): JSX.Element { +export function TimeSelectors({ + control, + errors, + trigger, + clearErrors, + validateEndAt, +}: TimeSelectorsProps): JSX.Element { + const errorMessages = []; + if (errors.startAt) { + errorMessages.push(errors.startAt.message); + } + if (errors.endAt) { + errorMessages.push(errors.endAt.message); + } + return ( -
-
- ( - { - field.onChange(value); - clearErrors("endAt"); - trigger("endAt"); - }} - isError={Boolean(error)} - errorMessage={error?.message} - > - - {field.value || FORM_LABELS.selectStartTime} - - - {timeOptions.map((time) => ( - - {time} - - ))} - - - )} - /> -
+
+
+
+ ( + { + field.onChange(value); + clearErrors("endAt"); + trigger("endAt"); + }} + isError={Boolean(errors.startAt)} + > + + {field.value || FORM_LABELS.selectStartTime} + + + {timeOptions.map((time) => ( + + {time} + + ))} + + + )} + /> +
-
- true, // 검증 로직을 메인 컴포넌트에서 처리 - }} - render={({ field, fieldState: { error } }) => ( - { - field.onChange(value); - }} - isError={Boolean(error)} - errorMessage={error?.message} - > - - {field.value || FORM_LABELS.selectEndTime} - - - {timeOptions.map((time) => ( - - {time} - - ))} - - - )} - /> +
+ ( + { + field.onChange(value); + trigger("endAt"); + }} + isError={Boolean(errors.endAt)} + > + + {field.value || FORM_LABELS.selectEndTime} + + + {timeOptions.map((time) => ( + + {time} + + ))} + + + )} + /> +
+ + {errorMessages.length > 0 && ( +
+ {errorMessages.map((msg, _) => ( +
{msg}
+ ))} +
+ )}
); } diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx index 4f62a454..5a45c3b9 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableDesktop.tsx @@ -1,7 +1,6 @@ "use client"; import { type TBaseItem, type IReservation } from "@repo/types"; - import RoomName from "../RoomName"; import ScheduleRow from "../ScheduleRow"; import CurrentTimeIndicator from "../ScheduleRow/CurrentTimeIndicator"; diff --git a/apps/web/app/types/ReservationFormTypes.ts b/apps/web/app/types/ReservationFormTypes.ts index 702f55a9..0d70d14e 100644 --- a/apps/web/app/types/ReservationFormTypes.ts +++ b/apps/web/app/types/ReservationFormTypes.ts @@ -26,6 +26,7 @@ export interface TimeSelectorsProps { errors: FieldErrors; trigger: UseFormTrigger; clearErrors: UseFormClearErrors; + validateEndAt: (endAt: string) => boolean | string; } export interface AttendeesMultiSelectProps { diff --git a/apps/web/app/utils/validateTime.ts b/apps/web/app/utils/validateTime.ts index a4527ba2..398eac46 100644 --- a/apps/web/app/utils/validateTime.ts +++ b/apps/web/app/utils/validateTime.ts @@ -61,7 +61,7 @@ export function validateEndAt({ let itemId: string; if (typeof reservation.item === "string") { itemId = reservation.item; - } else if (reservation.item && "_id" in reservation.item) { + } else if ("_id" in reservation.item) { itemId = reservation.item._id; } else { return false; @@ -71,7 +71,6 @@ export function validateEndAt({ return false; } - // 수정된 부분: selectedReservationId를 사용하여 현재 예약을 제외 if (selectedReservationId === reservation._id) { return false; } From 2b8e8920736ce8bdee2a93b4056eb4b3a80b46e6 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 20:52:37 +0900 Subject: [PATCH 64/72] Chore: pnpm install --- pnpm-lock.yaml | 214 +++++++++++++++++++++++++------------------------ 1 file changed, 110 insertions(+), 104 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2ac05074..0c84f8bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,7 +218,7 @@ importers: version: 13.0.3(expo@51.0.38(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))) nativewind: specifier: ^4.1.21 - version: 4.1.23(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))) + version: 4.1.23(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(typescript@5.3.3))) react: specifier: ^18.2.0 version: 18.3.1 @@ -245,7 +245,7 @@ importers: version: 0.19.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) tailwindcss: specifier: ^3.4.14 - version: 3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + version: 3.4.14(ts-node@10.9.2(typescript@5.3.3)) devDependencies: '@babel/core': specifier: ^7.20.0 @@ -276,10 +276,10 @@ importers: version: 7.1.2(eslint@8.57.1)(typescript@5.3.3) jest: specifier: ^29.2.1 - version: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + version: 29.7.0(ts-node@10.9.2(typescript@5.3.3)) jest-expo: specifier: ~51.0.3 - version: 51.0.4(@babel/core@7.26.0)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)))(react@18.3.1) + version: 51.0.4(@babel/core@7.26.0)(jest@29.7.0(ts-node@10.9.2(typescript@5.3.3)))(react@18.3.1) react-test-renderer: specifier: 18.2.0 version: 18.2.0(react@18.3.1) @@ -410,6 +410,9 @@ importers: cookies-next: specifier: ^4.3.0 version: 4.3.0 + date-fns: + specifier: ^4.1.0 + version: 4.1.0 es-toolkit: specifier: ^1.26.1 version: 1.26.1 @@ -560,7 +563,7 @@ importers: version: 7.18.0(eslint@8.57.1)(typescript@5.2.2) '@vercel/style-guide': specifier: ^6.0.0 - version: 6.0.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(prettier@3.3.3)(typescript@5.2.2) + version: 6.0.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(jest@29.7.0)(prettier@3.3.3)(typescript@5.2.2) eslint-config-prettier: specifier: ^9.1.0 version: 9.1.0(eslint@8.57.1) @@ -569,7 +572,7 @@ importers: version: 2.2.3(eslint@8.57.1) eslint-config-universe: specifier: ^13.0.0 - version: 13.0.0(@types/eslint@8.56.12)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)(prettier@3.3.3)(typescript@5.2.2) + version: 13.0.0(@types/eslint@8.56.12)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3)(typescript@5.2.2) eslint-import-resolver-typescript: specifier: ^3.6.3 version: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1) @@ -614,7 +617,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + version: 29.7.0(ts-node@10.9.2(typescript@5.2.2)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -623,7 +626,7 @@ importers: version: 18.3.1(react@18.3.1) ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(ts-node@10.9.2(typescript@5.2.2)))(typescript@5.2.2) typescript: specifier: 5.2.2 version: 5.2.2 @@ -642,7 +645,7 @@ importers: dependencies: tailwindcss-rem-to-px: specifier: ^0.1.1 - version: 0.1.1(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + version: 0.1.1(ts-node@10.9.2(typescript@5.2.2)) devDependencies: '@repo/typescript-config': specifier: workspace:* @@ -655,7 +658,7 @@ importers: version: 8.4.47 tailwindcss: specifier: ^3.4.0 - version: 3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + version: 3.4.14(ts-node@10.9.2(typescript@5.2.2)) typescript: specifier: 5.2.2 version: 5.2.2 @@ -4696,6 +4699,9 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -11613,7 +11619,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2))': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -11627,7 +11633,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -11648,7 +11654,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))': + '@jest/core@29.7.0(ts-node@10.9.2(typescript@5.2.2))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -11662,7 +11668,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.2.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -11683,7 +11689,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))': + '@jest/core@29.7.0(ts-node@10.9.2(typescript@5.3.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -11697,7 +11703,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.3.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -14101,7 +14107,7 @@ snapshots: graphql: 15.8.0 wonka: 4.0.15 - '@vercel/style-guide@6.0.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(prettier@3.3.3)(typescript@5.2.2)': + '@vercel/style-guide@6.0.0(@next/eslint-plugin-next@14.2.6)(eslint@8.57.1)(jest@29.7.0)(prettier@3.3.3)(typescript@5.2.2)': dependencies: '@babel/core': 7.26.0 '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.1) @@ -14109,13 +14115,13 @@ snapshots: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2) '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.2.2) eslint-config-prettier: 9.1.0(eslint@8.57.1) - eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0) + eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)) eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0)(typescript@5.2.2) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-playwright: 1.8.3(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2))(eslint@8.57.1) + eslint-plugin-playwright: 1.8.3(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0)(typescript@5.2.2))(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) eslint-plugin-testing-library: 6.4.0(eslint@8.57.1)(typescript@5.2.2) @@ -15044,13 +15050,13 @@ snapshots: optionalDependencies: typescript: 5.6.3 - create-jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + create-jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -15059,13 +15065,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)): + create-jest@29.7.0(ts-node@10.9.2(typescript@5.2.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.2.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -15074,13 +15080,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + create-jest@29.7.0(ts-node@10.9.2(typescript@5.3.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.3.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -15199,6 +15205,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 + date-fns@4.1.0: {} + dayjs@1.11.13: {} debug@2.6.9: @@ -15643,7 +15651,7 @@ snapshots: eslint: 8.57.1 eslint-plugin-turbo: 2.2.3(eslint@8.57.1) - eslint-config-universe@13.0.0(@types/eslint@8.56.12)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)(prettier@3.3.3)(typescript@5.2.2): + eslint-config-universe@13.0.0(@types/eslint@8.56.12)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3)(typescript@5.2.2): dependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2) '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.2.2) @@ -15663,7 +15671,7 @@ snapshots: - supports-color - typescript - eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0): + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)): dependencies: eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) @@ -15681,7 +15689,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -15700,7 +15708,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -15719,7 +15727,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -15738,7 +15746,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -15751,7 +15759,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -15762,7 +15770,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -15783,7 +15791,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -15794,7 +15802,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -15837,7 +15845,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -15866,7 +15874,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.3.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -15924,7 +15932,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -15942,13 +15950,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2): + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0)(typescript@5.2.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.2.2) eslint: 8.57.1 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2) - jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + jest: 29.7.0(ts-node@10.9.2(typescript@5.2.2)) transitivePeerDependencies: - supports-color - typescript @@ -15984,12 +15992,12 @@ snapshots: eslint-plugin-only-warn@1.1.0: {} - eslint-plugin-playwright@1.8.3(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2))(eslint@8.57.1): + eslint-plugin-playwright@1.8.3(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0)(typescript@5.2.2))(eslint@8.57.1): dependencies: eslint: 8.57.1 globals: 13.24.0 optionalDependencies: - eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(typescript@5.2.2))(eslint@8.57.1)(jest@29.7.0)(typescript@5.2.2) eslint-plugin-prettier@5.2.1(@types/eslint@8.56.12)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3): dependencies: @@ -17348,16 +17356,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + jest-cli@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + create-jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -17367,16 +17375,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)): + jest-cli@29.7.0(ts-node@10.9.2(typescript@5.2.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(typescript@5.2.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + create-jest: 29.7.0(ts-node@10.9.2(typescript@5.2.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.2.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -17386,16 +17394,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + jest-cli@29.7.0(ts-node@10.9.2(typescript@5.3.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(typescript@5.3.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + create-jest: 29.7.0(ts-node@10.9.2(typescript@5.3.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.3.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -17405,7 +17413,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -17431,12 +17439,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.6 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.2.2) + ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)): + jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.2.2)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -17462,12 +17470,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.6 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.3.3) + ts-node: 10.9.2(typescript@5.2.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(typescript@5.3.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 @@ -17493,7 +17501,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.17.6 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) + ts-node: 10.9.2(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -17541,7 +17549,7 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@51.0.4(@babel/core@7.26.0)(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)))(react@18.3.1): + jest-expo@51.0.4(@babel/core@7.26.0)(jest@29.7.0(ts-node@10.9.2(typescript@5.3.3)))(react@18.3.1): dependencies: '@expo/config': 9.0.4 '@expo/json-file': 8.3.3 @@ -17550,7 +17558,7 @@ snapshots: find-up: 5.0.0 jest-environment-jsdom: 29.7.0 jest-watch-select-projects: 2.0.0 - jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))) + jest-watch-typeahead: 2.2.1(jest@29.7.0(ts-node@10.9.2(typescript@5.3.3))) json5: 2.2.3 lodash: 4.17.21 react-test-renderer: 18.2.0(react@18.3.1) @@ -17739,11 +17747,11 @@ snapshots: chalk: 3.0.0 prompts: 2.4.2 - jest-watch-typeahead@2.2.1(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))): + jest-watch-typeahead@2.2.1(jest@29.7.0(ts-node@10.9.2(typescript@5.3.3))): dependencies: ansi-escapes: 6.2.1 chalk: 4.1.2 - jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + jest: 29.7.0(ts-node@10.9.2(typescript@5.3.3)) jest-regex-util: 29.6.3 jest-watcher: 29.7.0 slash: 5.1.0 @@ -17768,36 +17776,36 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + jest-cli: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)): + jest@29.7.0(ts-node@10.9.2(typescript@5.2.2)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(typescript@5.2.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + jest-cli: 29.7.0(ts-node@10.9.2(typescript@5.2.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + jest@29.7.0(ts-node@10.9.2(typescript@5.3.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.2(typescript@5.3.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-cli: 29.7.0(ts-node@10.9.2(typescript@5.3.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -18582,12 +18590,12 @@ snapshots: nanoid@3.3.7: {} - nativewind@4.1.23(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))): + nativewind@4.1.23(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(typescript@5.3.3))): dependencies: comment-json: 4.2.5 debug: 4.3.7(supports-color@5.5.0) - react-native-css-interop: 0.1.22(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))) - tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + react-native-css-interop: 0.1.22(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(typescript@5.3.3))) + tailwindcss: 3.4.14(ts-node@10.9.2(typescript@5.3.3)) transitivePeerDependencies: - react - react-native @@ -19040,29 +19048,29 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.47 - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: lilconfig: 3.1.2 yaml: 2.6.0 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.2.2) + ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(typescript@5.2.2)): dependencies: lilconfig: 3.1.2 yaml: 2.6.0 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.3.3) + ts-node: 10.9.2(typescript@5.2.2) - postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(typescript@5.3.3)): dependencies: lilconfig: 3.1.2 yaml: 2.6.0 optionalDependencies: postcss: 8.4.47 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) + ts-node: 10.9.2(typescript@5.3.3) postcss-nested@6.2.0(postcss@8.4.47): dependencies: @@ -19298,7 +19306,7 @@ snapshots: framer-motion: 11.11.11(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 - react-native-css-interop@0.1.22(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3))): + react-native-css-interop@0.1.22(react-native-reanimated@3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1))(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.14(ts-node@10.9.2(typescript@5.3.3))): dependencies: '@babel/helper-module-imports': 7.25.9 '@babel/traverse': 7.25.9 @@ -19309,7 +19317,7 @@ snapshots: react-native: 0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1) react-native-reanimated: 3.10.1(@babel/core@7.26.0)(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1) semver: 7.6.3 - tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + tailwindcss: 3.4.14(ts-node@10.9.2(typescript@5.3.3)) optionalDependencies: react-native-safe-area-context: 4.10.5(react-native@0.74.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1) transitivePeerDependencies: @@ -20337,13 +20345,13 @@ snapshots: tailwind-merge@2.5.4: {} - tailwindcss-rem-to-px@0.1.1(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + tailwindcss-rem-to-px@0.1.1(ts-node@10.9.2(typescript@5.2.2)): dependencies: - tailwindcss: 3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + tailwindcss: 3.4.14(ts-node@10.9.2(typescript@5.2.2)) transitivePeerDependencies: - ts-node - tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)): + tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -20362,7 +20370,7 @@ snapshots: postcss: 8.4.47 postcss-import: 15.1.0(postcss@8.4.47) postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -20370,7 +20378,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)): + tailwindcss@3.4.14(ts-node@10.9.2(typescript@5.2.2)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -20389,7 +20397,7 @@ snapshots: postcss: 8.4.47 postcss-import: 15.1.0(postcss@8.4.47) postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3)) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(typescript@5.2.2)) postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -20397,7 +20405,7 @@ snapshots: transitivePeerDependencies: - ts-node - tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + tailwindcss@3.4.14(ts-node@10.9.2(typescript@5.3.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -20416,7 +20424,7 @@ snapshots: postcss: 8.4.47 postcss-import: 15.1.0(postcss@8.4.47) postcss-js: 4.0.1(postcss@8.4.47) - postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(typescript@5.3.3)) postcss-nested: 6.2.0(postcss@8.4.47) postcss-selector-parser: 6.1.2 resolve: 1.22.8 @@ -20587,12 +20595,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.26.0) esbuild: 0.20.2 - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)))(typescript@5.2.2): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(ts-node@10.9.2(typescript@5.2.2)))(typescript@5.2.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2)) + jest: 29.7.0(ts-node@10.9.2(typescript@5.2.2)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -20606,7 +20614,7 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.0) - ts-node@10.9.2(@types/node@20.17.6)(typescript@5.2.2): + ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -20620,47 +20628,45 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.2.2 + typescript: 5.6.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optional: true - ts-node@10.9.2(@types/node@20.17.6)(typescript@5.3.3): + ts-node@10.9.2(typescript@5.2.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.6 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.3.3 + typescript: 5.2.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optional: true - ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3): + ts-node@10.9.2(typescript@5.3.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.6 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.6.3 + typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + optional: true tsconfck@3.1.4(typescript@5.6.3): optionalDependencies: From e2e7d373cb7f41d1f6ec997530c9db75342cfac5 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Mon, 25 Nov 2024 21:31:24 +0900 Subject: [PATCH 65/72] =?UTF-8?q?Fix:=20build=20error=20=ED=95=B4=EA=B2=B0?= =?UTF-8?q?(import)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Reservation/ReservationForm/AttendeesMultiSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx b/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx index c67fdabe..053e3288 100644 --- a/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx +++ b/apps/web/app/meetings/_components/Reservation/ReservationForm/AttendeesMultiSelect.tsx @@ -1,4 +1,4 @@ -import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MulitiSelectDropdown"; +import MultiSelectDropdown from "@ui/src/components/common/Dropdown/MultiSelectDropdown"; import { Controller } from "react-hook-form"; import { Badge } from "@ui/index"; import { type IUser } from "@repo/types"; From 039ce1c7c481d4e544c188aee1a84327ece2535a Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:11:58 +0900 Subject: [PATCH 66/72] =?UTF-8?q?Feat:=20TimeText=20skeleton=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/skeleton/TimeTextSkeleton.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx diff --git a/apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx b/apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx new file mode 100644 index 00000000..4a4401dc --- /dev/null +++ b/apps/web/app/meetings/_components/skeleton/TimeTextSkeleton.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { memo } from "react"; + +function TimeTextSkeleton(): JSX.Element { + return ( +
+ {Array.from({ length: 24 }, (_, index) => ( +
+
+
+ ))} +
+ ); +} + +export default memo(TimeTextSkeleton); From 47c3efb897859b4dc3b214b9fee6e92377ea4b69 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:12:58 +0900 Subject: [PATCH 67/72] =?UTF-8?q?Feat:=20RoomName=20skeleton=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/skeleton/RoomNameSkeleton.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx diff --git a/apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx b/apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx new file mode 100644 index 00000000..300a5b7d --- /dev/null +++ b/apps/web/app/meetings/_components/skeleton/RoomNameSkeleton.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { memo } from "react"; + +function RoomNameSkeleton(): JSX.Element { + return ( +
+
+
+ ); +} + +export default memo(RoomNameSkeleton); From 5c113e8cde6d79dd698a19f5bf519a45cd56293f Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:14:22 +0900 Subject: [PATCH 68/72] =?UTF-8?q?Feat:=20Slot=20Skeleton=20=EC=A0=9C?= =?UTF-8?q?=EC=9E=91=20=EB=B0=8F=20=EB=94=94=EC=9E=90=EC=9D=B8=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skeleton/ScheduleSlotSkeleton.tsx | 20 ++++++++++++ .../meetings/_components/skeleton/index.tsx | 31 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx create mode 100644 apps/web/app/meetings/_components/skeleton/index.tsx diff --git a/apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx b/apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx new file mode 100644 index 00000000..39966f6e --- /dev/null +++ b/apps/web/app/meetings/_components/skeleton/ScheduleSlotSkeleton.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { memo } from "react"; + +function ScheduleSlotSkeleton(): JSX.Element { + return ( +
+ {Array.from({ length: 7 }, (_, index) => ( +
+
+
+
+
+
+ ))} +
+ ); +} + +export default memo(ScheduleSlotSkeleton); diff --git a/apps/web/app/meetings/_components/skeleton/index.tsx b/apps/web/app/meetings/_components/skeleton/index.tsx new file mode 100644 index 00000000..5cd0f519 --- /dev/null +++ b/apps/web/app/meetings/_components/skeleton/index.tsx @@ -0,0 +1,31 @@ +/* eslint-disable react/no-array-index-key */ +import { memo } from "react"; +import RoomNameSkeleton from "./RoomNameSkeleton"; +import ScheduleSlotSkeleton from "./ScheduleSlotSkeleton"; +import TimeTextSkeleton from "./TimeTextSkeleton"; + +function MeetingsSkeleton(): JSX.Element { + return ( +
+
+
+ {Array.from({ length: 7 }).map((_, index) => ( +
+ +
+ ))} +
+
+ + {Array.from({ length: 1 }).map((_, index) => ( +
+ +
+ ))} +
+
+
+ ); +} + +export default memo(MeetingsSkeleton); From 2ffd91e4890e1b7c90c64996b2699f0e8f838ba2 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:15:42 +0900 Subject: [PATCH 69/72] =?UTF-8?q?Feat:=20skeleton=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/meetings/_components/MeetingRoomSchedule.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx b/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx index ae189c1b..caa2bf0e 100644 --- a/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx +++ b/apps/web/app/meetings/_components/MeetingRoomSchedule.tsx @@ -8,6 +8,7 @@ import { formatDate } from "@/app/utils/formatDate"; import { getAllItems } from "@/api/items"; import { getReservationsByTypeAndDate } from "@/api/reservations"; import ScheduleTable from "./Schedule/ScheduleTable"; +import MeetingsSkeleton from "./skeleton"; export default function MeetingRoomSchedule(): JSX.Element { const { selectedDate } = useDateStore(); @@ -24,7 +25,7 @@ export default function MeetingRoomSchedule(): JSX.Element { queryFn: () => getAllItems({ itemType: MEETING_ROOMS_TYPE }), }); - if (meetingsIsLoading || roomsIsLoading) return
로딩~
; + if (meetingsIsLoading || roomsIsLoading) return ; return ; } From 740b31f858e2b2e1b9067910a84a686e8c4f4452 Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:25:10 +0900 Subject: [PATCH 70/72] Fix: eslint error --- apps/api/src/utils/multerConfig.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/api/src/utils/multerConfig.ts b/apps/api/src/utils/multerConfig.ts index 044f0aee..1ab6d449 100644 --- a/apps/api/src/utils/multerConfig.ts +++ b/apps/api/src/utils/multerConfig.ts @@ -1,5 +1,3 @@ -/* eslint-disable -- 일시적 eslint 에러 회피*/ - import { extname } from "node:path"; import multer, { type Multer } from "multer"; import multerS3, { AUTO_CONTENT_TYPE } from "multer-s3"; From 35b4ee8591644ccd8c1538cf82fd1809080688ae Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:34:37 +0900 Subject: [PATCH 71/72] =?UTF-8?q?Fix:=20Mobile=20Date=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx index a15d9403..e9031233 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleTable/ScheduleTableMobile.tsx @@ -23,7 +23,7 @@ export default function ScheduleTableMobile(props: ScheduleTableMobileProps): JS const isSameRoom = scheduleItemId === room._id; - const scheduleDate = new Date(schedule.startAt).toISOString().split("T")[0]; + const scheduleDate = new Date(schedule.startAt).toLocaleDateString("en-CA", { timeZone: "Asia/Seoul" }); const isSameDate = scheduleDate === selectedDate; return isSameRoom && isSameDate; From 3fbdd596754252a285db7caa476704064394bc8c Mon Sep 17 00:00:00 2001 From: YoungJun Bae Date: Thu, 28 Nov 2024 18:48:26 +0900 Subject: [PATCH 72/72] =?UTF-8?q?Style:=20TimeIndicator=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Schedule/ScheduleRow/CurrentTimeIndicator.tsx | 6 ++++-- .../app/meetings/_components/Schedule/ScheduleRow/index.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx index a392a1e2..a5ef9c21 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/CurrentTimeIndicator.tsx @@ -24,10 +24,12 @@ export default function CurrentTimeIndicator(props: CurrentTimeIndicatorProps): return (
- +
+ +
); } diff --git a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx index 3b048130..096e6571 100644 --- a/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx +++ b/apps/web/app/meetings/_components/Schedule/ScheduleRow/index.tsx @@ -61,7 +61,7 @@ export default function ScheduleRow(props: ScheduleRowProps): JSX.Element { />
-
+