Skip to content

Commit 2d8a276

Browse files
dudwns0213bokeeeey
andauthored
[Feat] 회의실 예약 api 연동 (#158)
* Feat: reservation api 경로 변경 및 API ENDPOINTS 적용 * Chore: reservation api rename(복수) * Feat: items api 구현 * Feat: meeting main에서 회의실, 해당 날짜 예약 현황 useQuery 작성 * Feat: ScheduleTable api 연동 * Feat: ScheduleTableDesktop api 연동 * Feat: ScheduleTableMobile api 연동 * Feat: ScheduleRow api 연동 및 time api에 맞게 수정 * Feat: Tooltip props children으로 변경 * Feat: ScheduleItem type 수정(api에 맞게) * Feat: DesktopReservationSheet interface 수정(api에 맞게) * Feat: MobileReservationSheet interface 수정(api에 맞게) * Feat: ReservationForm interface 수정 (api에 맞게) * Chore: 날짜 문자열 처리를 위한 date-fns install * Feat: parseISO 적용 * Feat: 현재 userId와 schedule의 userId 비교하여 표현(css 및 사이드바) * Feat: 예약되어 있는 회의 클릭 시 내용 표시 * Chore: query data, loading 이름 변경 * Fix: lint error(import 순서) * Feat: scheduletype 수정 및 room _id props로 전달 * Feat: ReservationForm rooms query로 데이터 연결 * Feat: getAllUser API 함수 구현 * Feat: getAllUser query로 데이터 연동 * Feat: 전체 사용자 Dropdown 선택 가능 기능 구현 * Fix: Dropdown storybook state type error * Feat: MultiSelectDropdown wrapper className props 추가 * Style: Profile className 추가(전역으로 h-auto 설정되는 문제 해결을 위해) * Feat: response에 맞게 데이터 조정 및 불필요한 코드 삭제 * Feat: createReservationMutation 구현 및 Form을 통해 예약되는 기능 구현 * Chore: createReservation interface 및 data 수정 * Remove: Modal notify remove(mutation에 존재하므로 삭제) * Fix: 09:00 이후의 시간대만 표시되는 문제 해결(parseISO에서 Date 사용) * Feat: 시작 시간에서 30분 뒤의 종료시간 자동 설정, error 처리 * Feat: ReservationForm 제출 데이터 state type 설정 * Feat: 예약 시 이미 예약된 스케줄을 침범하는 경우 error 표시 * Feat: ScheduleItem 대신 Slot에서 예약된 스케줄도 표시하도록 변경 * Feat: 예약 내용을 수정할updateReservation 함수 생성 * Feat: 예약된 스케줄을 클릭할 경우 버튼을 수정하기로 표시 * Fix: reservationId 전달되지 않던 error 수정 * Feat: deleteReservation 구현 * Feat: ReservationModal title, content 받을 수 있게 수정 * Feat: ReservationSheetContent 컴포넌트 생성으로 인한 로직 분리 * Feat: invalidateQueries를 통한 데이터 최신화(생성, 수정, 삭제 시) * Feat: ReservationForm 삭제하기 버튼 및 기능 구현 * Test: 예약 시간들 text로 확인해보기(임시) * Fix: type 및 자동 수정 * Feat: useRoomsData hooks 구현 * Feat: useMeetingsData hook 구현 * Feat: meetingroomstype 상수화 및 formatDate 유틸함수 생성 * Refactor: MeetingRoomSchedule 리팩토링 * Remove: query hook delete * Feat: getRoomSchedules 생성 및 컴포넌트 적용 * Feat: RoomName children 수정 * Feat: CurrentTimeLabel 컴포넌트 리팩토링 * Feat: useSlotReservations hook 구현 * Feat: timeToMinutes util 함수 생성 * Refactor: ScheduleRow 컴포넌트 리팩토링 * Refactor: ReservationSheetContent 리팩토링 * Feat: reservationForm 내부 컴포넌트 리팩토링 * Feat: Form Type, constants, 유틸 함수 생성 * Refactor: Form, sheet 리팩토링 * Fix: 시간 파싱 에러로 인한 getRoomSchedule 삭제 * Fix: eslint error 해결 * Chore: pnpm install * Fix: build error 해결(import) * Feat: TimeText skeleton 제작 * Feat: RoomName skeleton 제작 * Feat: Slot Skeleton 제작 및 디자인에 맞게 구현 * Feat: skeleton 적용 * Fix: eslint error * Fix: Mobile Date 수정 * Style: TimeIndicator 수정 --------- Co-authored-by: 김보경 <rlaqhrud6263@naver.com>
1 parent 9a6554b commit 2d8a276

48 files changed

Lines changed: 1670 additions & 713 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/web/api/items.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,31 @@
11
import { API_ENDPOINTS } from "@repo/constants";
2-
import { type ISeat } from "@repo/types";
2+
import {
3+
type TBaseItem,
4+
type TItemType,
5+
type IRoom,
6+
type ISeat,
7+
type IEquipment,
8+
type MessageResponse,
9+
} from "@repo/types";
310
import { axiosRequester } from "@/lib/axios";
411

12+
// 특정 타입의 아이템 조회
13+
interface GetAllItemsParams {
14+
itemType: TItemType;
15+
}
16+
17+
export const getAllItems = async (params: GetAllItemsParams): Promise<TBaseItem[]> => {
18+
const { itemType } = params;
19+
const { data } = await axiosRequester<TBaseItem[]>({
20+
options: {
21+
method: "GET",
22+
url: API_ENDPOINTS.ITEMS.GET_ALL(itemType),
23+
},
24+
});
25+
26+
return data;
27+
};
28+
529
/**
630
* 모든 좌석 데이터를 가져옵니다.
731
* @returns 모든 좌석 데이터를 포함하는 Promise.
@@ -17,6 +41,59 @@ export const getAllSeats = async (): Promise<ISeat[]> => {
1741
return data;
1842
};
1943

44+
// 아이템 생성
45+
interface CreateItemParams {
46+
itemType: TItemType;
47+
itemData: Partial<IRoom | ISeat | IEquipment>;
48+
}
49+
50+
export const createItem = async (params: CreateItemParams): Promise<TBaseItem> => {
51+
const { itemType, itemData } = params;
52+
const { data } = await axiosRequester<TBaseItem>({
53+
options: {
54+
method: "POST",
55+
url: API_ENDPOINTS.ITEMS.CREATE_ITEM,
56+
data: {
57+
itemType,
58+
...itemData,
59+
},
60+
},
61+
});
62+
63+
return data;
64+
};
65+
66+
// 아이템 업데이트
67+
interface UpdateItemParams {
68+
itemId: string;
69+
itemData: Partial<IRoom | ISeat | IEquipment>;
70+
}
71+
72+
export const updateItem = async (params: UpdateItemParams): Promise<TBaseItem> => {
73+
const { itemId, itemData } = params;
74+
const { data } = await axiosRequester<TBaseItem>({
75+
options: {
76+
method: "PATCH",
77+
url: API_ENDPOINTS.ITEMS.UPDATE_ITEM(itemId),
78+
data: itemData,
79+
},
80+
});
81+
82+
return data;
83+
};
84+
85+
// 아이템 삭제
86+
export const deleteItem = async (itemId: string): Promise<MessageResponse> => {
87+
const { data } = await axiosRequester<MessageResponse>({
88+
options: {
89+
method: "DELETE",
90+
url: API_ENDPOINTS.ITEMS.DELETE_ITEM(itemId),
91+
},
92+
});
93+
94+
return data;
95+
};
96+
2097
/**
2198
* 특정 아이템 데이터를 수정합니다.
2299
* @returns 수정 결과 메시지를 포함하는 Promise.
@@ -29,5 +106,6 @@ export const patchItem = async (itemId: string, formData: FormData): Promise<str
29106
data: formData,
30107
},
31108
});
109+
32110
return data;
33111
};

apps/web/api/reservations.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { API_ENDPOINTS } from "@repo/constants";
2+
import { type IReservation, type TReservationStatus } from "@repo/types/src/reservationType";
3+
import { axiosRequester } from "@/lib/axios";
4+
5+
// 특정 유저의 오늘 날짜 예약 전체 조회
6+
interface GetUserReservationsParams {
7+
userId: string;
8+
}
9+
10+
export const getUserReservations = async (params: GetUserReservationsParams): Promise<IReservation[]> => {
11+
const { userId } = params;
12+
const { data } = await axiosRequester<IReservation[]>({
13+
options: {
14+
method: "GET",
15+
url: API_ENDPOINTS.RESERVATION.GET_USER_RESERVATIONS(userId),
16+
},
17+
});
18+
19+
return data;
20+
};
21+
22+
// 아이템 타입 및 날짜에 대한 예약 조회
23+
interface GetReservationsByTypeAndDateParams {
24+
itemType: "room" | "seat" | "equipment";
25+
date: string;
26+
status?: TReservationStatus;
27+
}
28+
29+
export const getReservationsByTypeAndDate = async (
30+
params: GetReservationsByTypeAndDateParams,
31+
): Promise<IReservation[]> => {
32+
const { itemType, date, status } = params;
33+
const { data } = await axiosRequester<IReservation[]>({
34+
options: {
35+
method: "GET",
36+
url: API_ENDPOINTS.RESERVATION.GET_RESERVATIONS_BY_TYPE_AND_DATE(itemType, date),
37+
params: {
38+
status,
39+
},
40+
},
41+
});
42+
43+
return data;
44+
};
45+
46+
// 예약 생성
47+
export interface CreateReservationParams {
48+
itemId: string;
49+
savedReservation: IReservation;
50+
}
51+
52+
export interface CreateReservationRequest {
53+
userId: string;
54+
itemType: "room";
55+
startAt: string;
56+
endAt: string;
57+
status: "reserved";
58+
notes: string;
59+
attendees: string[];
60+
}
61+
62+
export interface CreateReservationResponse {
63+
message: string;
64+
savedReservation: IReservation;
65+
}
66+
67+
export const createReservation = async (
68+
itemId: string,
69+
reservationData: CreateReservationRequest,
70+
): Promise<IReservation> => {
71+
const { data } = await axiosRequester<CreateReservationResponse>({
72+
options: {
73+
method: "POST",
74+
url: API_ENDPOINTS.RESERVATION.CREATE_RESERVATION(itemId),
75+
data: reservationData,
76+
},
77+
});
78+
79+
return data.savedReservation;
80+
};
81+
82+
export interface UpdateReservationRequest {
83+
startAt?: string;
84+
endAt?: string;
85+
status?: TReservationStatus;
86+
notes?: string;
87+
attendees?: string[];
88+
}
89+
90+
export interface UpdateReservationResponse {
91+
message: string;
92+
updatedReservation: IReservation;
93+
}
94+
95+
export const updateReservation = async (
96+
reservationId: string,
97+
reservationData: UpdateReservationRequest,
98+
): Promise<IReservation> => {
99+
const { data } = await axiosRequester<UpdateReservationResponse>({
100+
options: {
101+
method: "PATCH",
102+
url: API_ENDPOINTS.RESERVATION.UPDATE_RESERVATION(reservationId),
103+
data: reservationData,
104+
},
105+
});
106+
107+
return data.updatedReservation;
108+
};
109+
110+
export const deleteReservation = async (reservationId: string): Promise<void> => {
111+
await axiosRequester({
112+
options: {
113+
method: "DELETE",
114+
url: API_ENDPOINTS.RESERVATION.DELETE_RESERVATION(reservationId),
115+
},
116+
});
117+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useEffect, useState } from "react";
2+
3+
interface UseCurrentTimePositionProps {
4+
slotWidth: number;
5+
startHour: number;
6+
endHour: number;
7+
}
8+
9+
interface UseCurrentTimePositionReturn {
10+
currentPosition: number | null;
11+
currentTime: string;
12+
}
13+
14+
export function useCurrentTimePosition({
15+
slotWidth,
16+
startHour,
17+
endHour,
18+
}: UseCurrentTimePositionProps): UseCurrentTimePositionReturn {
19+
const [currentPosition, setCurrentPosition] = useState<number | null>(null);
20+
const [currentTime, setCurrentTime] = useState<string>("");
21+
22+
useEffect(() => {
23+
const updatePosition = (): void => {
24+
const now = new Date();
25+
const currentMinutes = now.getHours() * 60 + now.getMinutes();
26+
const totalMinutes = (endHour - startHour) * 60;
27+
28+
if (currentMinutes < startHour * 60 || currentMinutes >= endHour * 60) {
29+
setCurrentPosition(null);
30+
setCurrentTime("");
31+
return;
32+
}
33+
34+
const relativeMinutes = currentMinutes - startHour * 60;
35+
const scheduleWidth = slotWidth * (endHour - startHour) * 2; // Assuming 30-minute slots
36+
const position = (relativeMinutes / totalMinutes) * scheduleWidth;
37+
38+
setCurrentPosition(position);
39+
40+
const hours = String(now.getHours()).padStart(2, "0");
41+
const minutes = String(now.getMinutes()).padStart(2, "0");
42+
setCurrentTime(`${hours}:${minutes}`);
43+
};
44+
45+
updatePosition();
46+
const interval = setInterval(updatePosition, 60000);
47+
48+
return () => {
49+
clearInterval(interval);
50+
};
51+
}, [slotWidth, startHour, endHour]);
52+
53+
return { currentPosition, currentTime };
54+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
2+
import { useMemo } from "react";
3+
import { type IReservation } from "@repo/types";
4+
import { timeToMinutes } from "../utils/timeToMinutes";
5+
6+
/**
7+
* 예약 데이터를 슬롯 인덱스에 매핑하는 커스텀 훅
8+
* @param schedules 예약 데이터 배열
9+
* @param startHour 시작 시간
10+
* @param endHour 종료 시간
11+
* @param minutesPerSlot 슬롯당 분 단위
12+
* @returns 슬롯별 예약 상태 배열
13+
*/
14+
export const useSlotReservations = (
15+
schedules: IReservation[],
16+
startHour: number,
17+
endHour: number,
18+
minutesPerSlot: number,
19+
): (IReservation | null)[] => {
20+
return useMemo(() => {
21+
const totalSlots = (endHour - startHour) * 2; // 30분 단위
22+
const slotReservations: (IReservation | null)[] = Array(totalSlots).fill(null);
23+
24+
schedules.forEach((schedule) => {
25+
const startMinutes = timeToMinutes(schedule.startAt) - startHour * 60;
26+
const endMinutes = timeToMinutes(schedule.endAt) - startHour * 60;
27+
28+
const startIndex = Math.floor(startMinutes / minutesPerSlot);
29+
const endIndex = Math.ceil(endMinutes / minutesPerSlot);
30+
31+
for (let i = startIndex; i < endIndex; i++) {
32+
if (i >= 0 && i < totalSlots) {
33+
slotReservations[i] = schedule;
34+
}
35+
}
36+
});
37+
38+
return slotReservations;
39+
}, [schedules, startHour, endHour, minutesPerSlot]);
40+
};

apps/web/app/api/reservation.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const MEETING_ROOMS_TYPE = "room";

0 commit comments

Comments
 (0)