Repository: Fit-Links/FitLink-FE
Pull request #92: [FILZ-114/trainer] feat: 트레이너 도메인 reservation DTO 타입 정의 및 API 함수 구현
Author: login: choi-ik
Labels: name: ✨feat
description: 새로운 기능 추가
Created: 2025-03-04T06:37:47Z | Merged: 2025-03-07T05:53:24Z

## 📝 작업 내용

### reservation DTO 타입 정의
트레이너 도메인에서 사용되는 reservation DTO 타입을 정의하였습니다.
- 트레이너와 유저 도메인에서 동일하게 사용할 수 있는 타입을 `packages/core/src/api/types/common.ts`에추가로 정의해 놓았습니다.
- request로 params만 있다면 접미사로 `params`를, request body만 있다면 `request`를, path variables만 있다면 `path parmas`라는 접미사를 붙혀주었습니다. reqeust body + path variabels가 공존하는 경우에는 `request`라는 접미사를 붙혀주었는데 네이밍 컨벤션에 대해 이번 PR에서 다시 정립하는 게 좋을 것 같다는 생각입니다.
- response 타입의 경우 실제로 api의 응답으로 주어지는 response는 `ApiResponse`라는 접미사를 붙혀주었고, ResponseBase타입에 제네릭으로 들어갈 타입은 `Response` 접미사만 붙혀서 작성하였습니다. 해당 네이밍도 이번 PR에서 컨벤션을 정하면 좋을 것 같아요.
- 빠르게 코드의 의미를 파악할 수 있도록 주석을 작성해두었습니다. API 명세서에 나와있는 것과 동일하게 작성하였으므로 타입을 비교한 뒤 피드백 부탁드립니다. Approve 이후 주석은 모두 제거할 예정입니다.
- API 명세서를 보다보니 헷갈리는 부분이 있었습니다. ReservationStatus에 관한 내용인데, API 명세에서 전반적으로 ReservationStatus가 동일하게 사용되는 부분이 있고 사용되지 않는 부분이 있는 것 같아요. 해당 내용에 대해서도 의논해보면 좋을 것 같습니다.

### reservation API 함수 구현
reservation DTO type을 사용하여 reservation API 함수를 구현하였습니다.
- 해당 함수는 `packages/core/src/api/core.ts`의 `axios instance`를 import하여 사용하고 있으며, 해당 instance를 좀 더 효율적으로 사용하기 위해 `apiClient` 유틸 함수를 구현하였습니다. 해당 유틸 함수는 `baseUrl`과 `tokenProvider`를 주입하고 있으며 tokenProvider의 경우 추후 로그인 기능을 구현하시는 분께서 토큰을 어디에서 get 할지 설계 후 로직을 작성하시면 될 것 같습니다.
- API 함수도 편의를 위해 주석을 달아놓았습니다. 마찬가지로 Approve 이후 주석은 모두 제거할 예정입니다.
추가로, api 함수의 네이밍을 http 메소드(get/post/delete/patch/put)에 맞도록 접두사 컨벤션을 정하는게 좋을 것 같아요!

## 💬 리뷰 요구사항(선택)
- 타입을 추상화한 부분에 대한 피드백 부탁드립니다.



Diff:
diff --git a/apps/trainer/package.json b/apps/trainer/package.json
index 71d5eaba..dd8727cf 100644
--- a/apps/trainer/package.json
+++ b/apps/trainer/package.json
@@ -18,6 +18,7 @@
   },
   "dependencies": {
     "@5unwan/ui": "workspace:*",
+    "@5unwan/core": "workspace:*",
     "@tanstack/react-query": "^5.62.7",
     "axios": "^1.7.9",
     "date-fns": "^4.1.0",
diff --git a/apps/trainer/services/core.ts b/apps/trainer/services/core.ts
new file mode 100644
index 00000000..298af12d
--- /dev/null
+++ b/apps/trainer/services/core.ts
@@ -0,0 +1,16 @@
+import http, { initCoreApi } from "@5unwan/core/api/core";
+
+const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL;
+
+if (!baseUrl) {
+  throw new Error("NEXT_PUBLIC_API_BASE_URL 환경 변수가 설정되어 있지 않습니다.");
+}
+
+/** TODO: 토큰을 어떻게 가져올 것인지 로그인 기능 담당자가 추후 구현 */
+const tokenProvider = () => {
+  return null;
+};
+
+initCoreApi({ baseUrl, tokenProvider });
+
+export default http;
diff --git a/apps/trainer/services/reservations.ts b/apps/trainer/services/reservations.ts
new file mode 100644
index 00000000..2d7b4137
--- /dev/null
+++ b/apps/trainer/services/reservations.ts
@@ -0,0 +1,140 @@
+const RESERVATION_BASE_URL = "reservations";
+
+import http from "./core";
+import {
+  ReservationDetailStatusApiResponse,
+  ReservationStatusApiResponse,
+  ReservationDetailPendingStatusApiResponse,
+  ReservationSetNotAvailableApiResponse,
+  DirectReservationApiResponse,
+  FixReservationApiResponse,
+  CancelReservationApiResponse,
+  ApproveReservationApiResponse,
+  CompletedPtApiResponse,
+  ConfirmReservationChangeApiResponse,
+  ReservationStatusRequestQuery,
+  ReservationDetailStatusRequestPath,
+  ReservationDetailPendingStatusRequestPath,
+  ReservationSetNotAvailableRequestBody,
+  DirectReservationRequestBody,
+  FixReservationRequestBody,
+  CancelReservationRequestBody,
+  CancelReservationRequestPath,
+  ApproveReservationRequestBody,
+  ApproveReservationRequestPath,
+  CompletedPtRequestBody,
+  CompletedPtRequestPath,
+  ConfirmReservationChangeRequestBody,
+  ConfirmReservationChangeRequestPath,
+} from "./types/reservations";
+
+// 예약 현황 조회
+export const getReservationStatus = ({ date }: ReservationStatusRequestQuery) => {
+  http.get<ReservationStatusApiResponse>({ url: `${RESERVATION_BASE_URL}`, params: { date } });
+};
+
+// 예약 상세 조회
+export const getReservationDetailStatus = ({
+  reservationId,
+}: ReservationDetailStatusRequestPath) => {
+  http.get<ReservationDetailStatusApiResponse>({
+    url: `${RESERVATION_BASE_URL}/${reservationId}`,
+  });
+};
+
+// 예약 상세 대기 조회
+export const getReservationDetailStatusPendingStatus = ({
+  reservationId,
+}: ReservationDetailPendingStatusRequestPath) => {
+  http.get<ReservationDetailPendingStatusApiResponse>({
+    url: `${RESERVATION_BASE_URL}/${reservationId}`,
+  });
+};
+
+// 예약 불가 설정
+export const createReservationSetNotAvailable = ({
+  date,
+}: ReservationSetNotAvailableRequestBody) => {
+  http.post<ReservationSetNotAvailableApiResponse>({
+    url: `${RESERVATION_BASE_URL}/availability/disable`,
+    data: {
+      date,
+    },
+  });
+};
+
+// 직접 예약
+export const createDirectReservation = ({ reservations }: DirectReservationRequestBody) => {
+  http.post<DirectReservationApiResponse>({
+    url: `${RESERVATION_BASE_URL}`,
+    data: { reservations },
+  });
+};
+
+// 고정 예약
+export const createFixReservation = ({
+  memberId,
+  name,
+  reservations,
+}: FixReservationRequestBody) => {
+  http.post<FixReservationApiResponse>({
+    url: `${RESERVATION_BASE_URL}/fixed-reservation`,
+    data: { memberId, name, reservations },
+  });
+};
+
+// 예약 취소
+export const createCancelReservation = (
+  requestPath: CancelReservationRequestPath,
+  requestBody: CancelReservationRequestBody,
+) => {
+  const { reservationId } = requestPath;
+  const { cancel_reason } = requestBody;
+
+  http.post<CancelReservationApiResponse>({
+    url: `${RESERVATION_BASE_URL}/${reservationId}/cancel`,
+    data: { cancel_reason },
+  });
+};
+
+// 예약 승인
+export const createApproveReservation = (
+  requestPath: ApproveReservationRequestPath,
+  reqeustBody: ApproveReservationRequestBody,
+) => {
+  const { reservationId } = requestPath;
+  const { memberId, trainerId } = reqeustBody;
+
+  http.post<ApproveReservationApiResponse>({
+    url: `${RESERVATION_BASE_URL}/${reservationId}/approve`,
+    data: { memberId, trainerId },
+  });
+};
+
+// 진행한 PT 처리
+export const createCompletedPt = (
+  requestPath: CompletedPtRequestPath,
+  requestBody: CompletedPtRequestBody,
+) => {
+  const { sessionId } = requestPath;
+  const { isJoin } = requestBody;
+
+  http.post<CompletedPtApiResponse>({
+    url: `${RESERVATION_BASE_URL}/${sessionId}/complete`,
+    data: { isJoin },
+  });
+};
+
+// 예약 변경 승인
+export const createConfirmReservationChange = (
+  requestPath: ConfirmReservationChangeRequestPath,
+  requestBody: ConfirmReservationChangeRequestBody,
+) => {
+  const { reservationId } = requestPath;
+  const { memberId, trainerId } = requestBody;
+
+  http.post<ConfirmReservationChangeApiResponse>({
+    url: `${RESERVATION_BASE_URL}/${reservationId}/changes/apporove`,
+    data: { memberId, trainerId },
+  });
+};
diff --git a/apps/trainer/services/types/reservations.ts b/apps/trainer/services/types/reservations.ts
new file mode 100644
index 00000000..45b97615
--- /dev/null
+++ b/apps/trainer/services/types/reservations.ts
@@ -0,0 +1,164 @@
+import {
+  BaseMemberInfo,
+  BaseReservationDetail,
+  BaseReservationListItem,
+  DayOfWeek,
+  DetailedMemberInfo,
+  ReservationPathParams,
+  ReservationStatus,
+  ResponseBase,
+} from "@5unwan/core/api/types/common";
+
+// 요청 파라미터 (param)
+export type CalendarParams = {
+  today: string;
+  type: "trainer";
+};
+// 응답 데이터
+export type CalendarResponse = {
+  weekend: {
+    dayOfWeek: DayOfWeek;
+    date: string;
+  }[];
+};
+export type CalendarApiResponse = ResponseBase<CalendarResponse>;
+
+/* 예약 현황 조회 (리스트) */
+// 요청 파라미터(param)
+export type ReservationStatusRequestQuery = {
+  date?: string;
+};
+// 응답 데이터
+type ReservationStatusResponse = {
+  reservations: Omit<BaseReservationListItem, "memberInfo" | "sessionInfoId">[] & {
+    sessionInfoId: number | null;
+    memberInfo: Partial<BaseMemberInfo>;
+  };
+};
+export type ReservationStatusApiResponse = ResponseBase<ReservationStatusResponse>;
+
+/* 예약 상세 조회 (예약 확정 또는 수업 완료 상태) */
+// 요청 파라미터(path param)
+export type ReservationDetailStatusRequestPath = ReservationPathParams;
+// 응답 데이터
+type ReservationDetailStatusResponse = BaseReservationDetail<
+  Extract<ReservationStatus, "예약 확정" | "수업 완료">,
+  DetailedMemberInfo
+> & {
+  priority: number;
+};
+export type ReservationDetailStatusApiResponse = ResponseBase<ReservationDetailStatusResponse>;
+
+/* 예약 상세 대기 조회 */
+// 요청: 경로 변수(path param)
+export type ReservationDetailPendingStatusRequestPath = ReservationPathParams;
+// 응답 데이터
+type ReservationDetailPendingStatusResponse = {
+  waitingMembers: DetailedMemberInfo &
+    Pick<BaseReservationListItem, "reservationId" | "dayOfWeek"> &
+    {
+      reservationDates: string[];
+    }[];
+};
+export type ReservationDetailPendingStatusApiResponse =
+  ResponseBase<ReservationDetailPendingStatusResponse>;
+
+/* 예약 불가 설정 */
+// 요청: Request Body
+export type ReservationSetNotAvailableRequestBody = {
+  date: string;
+};
+// 응답 데이터
+type ReservationSetNotAvailableResponse = {
+  reservationId: number;
+};
+export type ReservationSetNotAvailableApiResponse =
+  ResponseBase<ReservationSetNotAvailableResponse>;
+
+/* 직접 예약 */
+// 요청: Request Body
+export type DirectReservationRequestBody = {
+  reservations: {
+    trainerId: number;
+    memberId: number;
+    name: string;
+    date: string[];
+    priority: number;
+  }[];
+};
+// 응답 데이터: 예약이 직접 확정된 경우 (예약 확정 상태만 포함)
+type DirectReservationResponse = {
+  reservation: {
+    reservationId: number;
+    status: Extract<ReservationStatus, "예약 확정">;
+  };
+};
+export type DirectReservationApiResponse = ResponseBase<DirectReservationResponse>;
+
+/* 고정 예약 */
+// 요청: Request Body
+export type FixReservationRequestBody = {
+  trainerId: number;
+  memberId: number;
+  name: string;
+  reservations: { date: string }[];
+};
+// 응답 데이터
+type FixReservationResponse = {
+  reservations: {
+    reservationId: number;
+  }[];
+};
+export type FixReservationApiResponse = ResponseBase<FixReservationResponse>;
+
+/* 예약 취소 */
+// 요청: 경로 변수와 Request Body 혼합
+export type CancelReservationRequestPath = ReservationPathParams;
+export type CancelReservationRequestBody = {
+  cancel_reason: string;
+};
+// 응답 데이터
+type CancelReservationResponse = {
+  reservationId: number;
+};
+export type CancelReservationApiResponse = ResponseBase<CancelReservationResponse>;
+
+/* 예약 승인 */
+// 요청: 경로 변수와 Request Body 혼합
+export type ApproveReservationRequestPath = ReservationPathParams;
+export type ApproveReservationRequestBody = {
+  trainerId: number;
+  memberId: number;
+};
+// 응답 데이터
+type ApproveReservationResponse = {
+  reservationId: number;
+};
+export type ApproveReservationApiResponse = ResponseBase<ApproveReservationResponse>;
+
+/* 진행한 PT 처리 */
+// 요청: 경로 변수와 Request Body 혼합
+export type CompletedPtRequestPath = {
+  sessionId: number;
+};
+export type CompletedPtRequestBody = {
+  isJoin: boolean;
+};
+// 응답 데이터
+type CompletedPtResponse = {
+  sessionId: number;
+};
+export type CompletedPtApiResponse = ResponseBase<CompletedPtResponse>;
+
+/* 예약 변경 승인 */
+// 요청: 경로 변수와 Request Body 혼합
+export type ConfirmReservationChangeRequestPath = ReservationPathParams;
+export type ConfirmReservationChangeRequestBody = {
+  trainerId: number;
+  memberId: number;
+};
+// 응답 데이터
+type ConfirmReservationChangeResponse = {
+  reservationId: number;
+};
+export type ConfirmReservationChangeApiResponse = ResponseBase<ConfirmReservationChangeResponse>;
diff --git a/packages/core/src/api/types/common.ts b/packages/core/src/api/types/common.ts
index 8a2a9f17..5d20f0f5 100644
--- a/packages/core/src/api/types/common.ts
+++ b/packages/core/src/api/types/common.ts
@@ -30,3 +30,47 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 예약 상태 (한글 리터럴)
+export type ReservationStatus = "예약 확정" | "예약 대기" | "예약 불가" | "수업 완료" | "휴뮤일";
+
+// 회원 정보 (기본)
+export type BaseMemberInfo = {
+  memberId: number;
+  name: string;
+};
+
+// 트레이너 등에서 사용하는 상세 회원 정보
+export type DetailedMemberInfo = BaseMemberInfo & {
+  birthdate: string;
+  phoneNumber: string;
+  profilePictureUrl: string;
+};
+
+// 공통 예약 리스트 항목 (트레이너, 유저 모두 사용)
+export type BaseReservationListItem = {
+  reservationId: number;
+  sessionInfoId: number;
+  isDayOff: boolean;
+  dayOfWeek: DayOfWeek;
+  reservationDate: string;
+  status: ReservationStatus;
+  memberInfo: BaseMemberInfo;
+};
+
+// 공통 예약 상세 타입 (예약 상세 조회 시)
+export type BaseReservationDetail<
+  TStatus extends ReservationStatus = ReservationStatus,
+  TMember = BaseMemberInfo,
+> = {
+  dayOfWeek: DayOfWeek;
+  reservationDate: string;
+  sessionId: number;
+  status: TStatus;
+  memberInfo: TMember;
+};
+
+// 경로(Path) 변수에 사용되는 공통 타입
+export type ReservationPathParams = {
+  reservationId: number;
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8c7690b9..6e9dc0a1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
 
   apps/trainer:
     dependencies:
+      '@5unwan/core':
+        specifier: workspace:*
+        version: link:../../packages/core
       '@5unwan/ui':
         specifier: workspace:*
         version: link:../../packages/ui
@@ -331,7 +334,7 @@ importers:
         version: 2.31.0(@typescript-eslint/parser@7.2.0(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))
       eslint-plugin-jest:
         specifier: ^28.9.0
-        version: 28.9.0(@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(jest@29.7.0(@types/node@22.13.5))(typescript@5.5.4)
+        version: 28.9.0(@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0))(typescript@5.5.4)
       eslint-plugin-jest-dom:
         specifier: ^5.5.0
         version: 5.5.0(@testing-library/dom@10.4.0)(eslint@9.17.0(jiti@2.4.1))
@@ -6706,42 +6709,6 @@ snapshots:
       jest-util: 29.7.0
       slash: 3.0.0
 
-  '@jest/core@29.7.0':
-    dependencies:
-      '@jest/console': 29.7.0
-      '@jest/reporters': 29.7.0
-      '@jest/test-result': 29.7.0
-      '@jest/transform': 29.7.0
-      '@jest/types': 29.6.3
-      '@types/node': 20.17.10
-      ansi-escapes: 4.3.2
-      chalk: 4.1.2
-      ci-info: 3.9.0
-      exit: 0.1.2
-      graceful-fs: 4.2.11
-      jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.17.10)
-      jest-haste-map: 29.7.0
-      jest-message-util: 29.7.0
-      jest-regex-util: 29.6.3
-      jest-resolve: 29.7.0
-      jest-resolve-dependencies: 29.7.0
-      jest-runner: 29.7.0
-      jest-runtime: 29.7.0
-      jest-snapshot: 29.7.0
-      jest-util: 29.7.0
-      jest-validate: 29.7.0
-      jest-watcher: 29.7.0
-      micromatch: 4.0.8
-      pretty-format: 29.7.0
-      slash: 3.0.0
-      strip-ansi: 6.0.1
-    transitivePeerDependencies:
-      - babel-plugin-macros
-      - supports-color
-      - ts-node
-    optional: true
-
   '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.5.4))':
     dependencies:
       '@jest/console': 29.7.0
@@ -8700,13 +8667,13 @@ snapshots:
       - supports-color
       - ts-node
 
-  create-jest@29.7.0(@types/node@22.13.5):
+  create-jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0):
     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@22.13.5)
+      jest-config: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -9174,7 +9141,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1):
+  eslint-module-utils@2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1):
     dependencies:
       debug: 3.2.7(supports-color@8.1.1)
     optionalDependencies:
@@ -9225,7 +9192,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.18.1(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1)
+      eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.18.1(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.1)
       hasown: 2.0.2
       is-core-module: 2.16.0
       is-glob: 4.0.3
@@ -9251,13 +9218,13 @@ snapshots:
     optionalDependencies:
       '@testing-library/dom': 10.4.0
 
-  eslint-plugin-jest@28.9.0(@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(jest@29.7.0(@types/node@22.13.5))(typescript@5.5.4):
+  eslint-plugin-jest@28.9.0(@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0))(typescript@5.5.4):
     dependencies:
       '@typescript-eslint/utils': 8.18.1(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4)
       eslint: 9.17.0(jiti@2.4.1)
     optionalDependencies:
       '@typescript-eslint/eslint-plugin': 7.2.0(@typescript-eslint/parser@7.2.0(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.17.0(jiti@2.4.1))(typescript@5.5.4)
-      jest: 29.7.0(@types/node@22.13.5)
+      jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -10233,16 +10200,16 @@ snapshots:
       - supports-color
       - ts-node
 
-  jest-cli@29.7.0(@types/node@22.13.5):
+  jest-cli@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0):
     dependencies:
-      '@jest/core': 29.7.0
+      '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.5.4))
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
       chalk: 4.1.2
-      create-jest: 29.7.0(@types/node@22.13.5)
+      create-jest: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)
       exit: 0.1.2
       import-local: 3.2.0
-      jest-config: 29.7.0(@types/node@22.13.5)
+      jest-config: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.7.2
@@ -10253,37 +10220,6 @@ snapshots:
       - ts-node
     optional: true
 
-  jest-config@29.7.0(@types/node@20.17.10):
-    dependencies:
-      '@babel/core': 7.26.7
-      '@jest/test-sequencer': 29.7.0
-      '@jest/types': 29.6.3
-      babel-jest: 29.7.0(@babel/core@7.26.7)
-      chalk: 4.1.2
-      ci-info: 3.9.0
-      deepmerge: 4.3.1
-      glob: 7.2.3
-      graceful-fs: 4.2.11
-      jest-circus: 29.7.0(babel-plugin-macros@3.1.0)
-      jest-environment-node: 29.7.0
-      jest-get-type: 29.6.3
-      jest-regex-util: 29.6.3
-      jest-resolve: 29.7.0
-      jest-runner: 29.7.0
-      jest-util: 29.7.0
-      jest-validate: 29.7.0
-      micromatch: 4.0.8
-      parse-json: 5.2.0
-      pretty-format: 29.7.0
-      slash: 3.0.0
-      strip-json-comments: 3.1.1
-    optionalDependencies:
-      '@types/node': 20.17.10
-    transitivePeerDependencies:
-      - babel-plugin-macros
-      - supports-color
-    optional: true
-
   jest-config@29.7.0(@types/node@20.17.10)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.5.4)):
     dependencies:
       '@babel/core': 7.26.7
@@ -10315,7 +10251,7 @@ snapshots:
       - babel-plugin-macros
       - supports-color
 
-  jest-config@29.7.0(@types/node@22.13.5):
+  jest-config@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0):
     dependencies:
       '@babel/core': 7.26.7
       '@jest/test-sequencer': 29.7.0
@@ -10588,12 +10524,12 @@ snapshots:
       - supports-color
       - ts-node
 
-  jest@29.7.0(@types/node@22.13.5):
+  jest@29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0):
     dependencies:
-      '@jest/core': 29.7.0
+      '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.5.4))
       '@jest/types': 29.6.3
       import-local: 3.2.0
-      jest-cli: 29.7.0(@types/node@22.13.5)
+      jest-cli: 29.7.0(@types/node@22.13.5)(babel-plugin-macros@3.1.0)
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros


Reviews:
user: login: yjc2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T01:49:18Z
user: login: choi-ik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T01:57:23Z
user: login: yjc2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T01:59:44Z
user: login: choi-ik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T02:01:22Z
user: login: yjc2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T02:13:56Z
user: login: yjc2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T02:14:39Z
user: login: yjc2021
body: > request로 params만 있다면 접미사로 params를, request body만 있다면 request를, path variables만 있다면 path parmas라는 접미사를 붙혀주었습니다. reqeust body + path variabels가 공존하는 경우에는 request라는 접미사를 붙혀주었는데 네이밍 컨벤션에 대해 이번 PR에서 다시 정립하는 게 좋을 것 같다는 생각입니다.

`params` 접미사를 사용 시 path parameter와 query parameter 중 어떤 것인지 애매하다고 생각합니다. [`queryParams`, `pathParams`] 또는 [`query`, `path`]는 어떠신가요?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T02:25:14Z
user: login: choi-ik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T02:37:58Z
user: login: yjc2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T02:50:46Z
user: login: choi-ik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T04:15:52Z
user: login: yjc2021
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T04:26:25Z
user: login: choi-ik
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T04:27:40Z
user: login: yjc2021
body: LGTM 🦖 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T05:52:26Z

Review comments:
diff_hunk: @@ -0,0 +1,161 @@
+import {
+  BaseMemberInfo,
+  BaseReservationDetail,
+  BaseReservationListItem,
+  DayOfWeek,
+  DetailedMemberInfo,
+  ISODateString,
+  IsoDateTimeString,
+  ReservationPathParams,
+  ReservationStatus,
+  ResponseBase,
+} from "@5unwan/core/api/types/common";
+
+// 요청 파라미터 (param)
+export type CalendarParams = {
+  today: ISODateString;
+  type: "trainer";
+};
+// 응답 데이터
+export type CalendarResponse = {
+  weekend: {
+    dayOfWeek: DayOfWeek;
+    date: ISODateString;
+  }[];
+};
+export type CalendarApiResponse = ResponseBase<CalendarResponse>;
+
+/* 예약 현황 조회 (리스트) */
+// 요청 파라미터(param)
+export type ReservationStatusParams = {
+  date?: ISODateString;
+};
+// 응답 데이터
+export type ReservationStatusResponse = {
+  reservations: Omit<BaseReservationListItem[], "memberInfo" | "sessionInfoId"> & {
path: apps/trainer/services/types/reservations.ts
user: login: yjc2021
body: `Omit<BaseReservationListItem, "memberInfo" | "sessionInfoId">[]` 가 아니라 `Omit<BaseReservationListItem[], "memberInfo" | "sessionInfoId">` 인가요?
created_at: 2025-03-07T01:49:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+import {
+  BaseMemberInfo,
+  BaseReservationDetail,
+  BaseReservationListItem,
+  DayOfWeek,
+  DetailedMemberInfo,
+  ISODateString,
+  IsoDateTimeString,
+  ReservationPathParams,
+  ReservationStatus,
+  ResponseBase,
+} from "@5unwan/core/api/types/common";
+
+// 요청 파라미터 (param)
+export type CalendarParams = {
+  today: ISODateString;
+  type: "trainer";
+};
+// 응답 데이터
+export type CalendarResponse = {
+  weekend: {
+    dayOfWeek: DayOfWeek;
+    date: ISODateString;
+  }[];
+};
+export type CalendarApiResponse = ResponseBase<CalendarResponse>;
+
+/* 예약 현황 조회 (리스트) */
+// 요청 파라미터(param)
+export type ReservationStatusParams = {
+  date?: ISODateString;
+};
+// 응답 데이터
+export type ReservationStatusResponse = {
+  reservations: Omit<BaseReservationListItem[], "memberInfo" | "sessionInfoId"> & {
path: apps/trainer/services/types/reservations.ts
user: login: choi-ik
body: 앗 전자가 맞습니다 ㅋㅋㅋㅋ 실수...
created_at: 2025-03-07T01:57:23Z
author_association: COLLABORATOR
path: apps/trainer/utils/apiClient.ts
user: login: yjc2021
body: apiClient.ts 파일을 utils가 아닌 services/core.ts 로 이전하는 건 어떨까요? 
- @5unwan/core 의 http가 아닌 user 내부 http를 사용해야 한다는 점을 인지할 수 있게 만들고 싶습니다
- api 요청 시 항상 사용되므로 utils 보다 services에 core.ts로 포함하는 것이 better colocation 이라고 생각합니다
created_at: 2025-03-07T01:59:44Z
author_association: CONTRIBUTOR
path: apps/trainer/utils/apiClient.ts
user: login: choi-ik
body: 좋습니다 용재님 의견이 더 좋은 응집성을 가질 것 같네요! 이전하도록 하겠습니다.
created_at: 2025-03-07T02:01:22Z
author_association: COLLABORATOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: yjc2021
body: 각 number가 몇자리 number인지 ts로 제한할 수 없는 점을 해결하기 위해 로직을 추가해야 할까요?
사용해 볼 수 있는 방안 2가지를 찾아봤는데
1. validation 함수 사용
``` ts
function isValidYear(y: string): y is Year {
    return /^\d{4}$/.test(y);
}
```
2. branded type 사용
```ts
type Year = `${number}${number}${number}${number}` & { __brand: 'Year' };

function createYear(y: string): Year {
    if (!/^\d{4}$/.test(y)) {
        throw new Error('Invalid Year format');
    }
    return y as Year;
}

let y = createYear('2023'); // OK
y = createYear('122222'); // 에러: Invalid Year format
```
created_at: 2025-03-07T02:13:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: yjc2021
body: 굳이 로직을 추가하지 않는 것도 하나의 방법이라고 생각해서 익님 생각도 궁금합니다
created_at: 2025-03-07T02:14:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: choi-ik
body: 빠르게 작성하는데 집중 하다보니 ts로는 자리수 제한을 하지 못한다는 걸 놓치고 코드를 작성했네요 ㅠ

저는 만약 현재 구현상황을 좀 더 개선한다면 2번 방식이 조금 더 좋을 것 같습니다. Year 타입 자체가 '4자리 숫자'만 입력 또는 출력이 될 수 있다는 것을 보장할 수는 없지만, createYear 함수가 테스트 코드로 충분히 테스트가 되고, 신뢰할 수 있다면 함수를 직접 실행해보지 않고도 저 함수에 Hover 하였을 때 리턴 받을 데이터의 타입을 예상할 수 있기 때문에 2번 방식이 조금 더 좋은 것 같습니다!

만약 2번 방식으로 적용을 하게 된다면 common.ts에는 type 만 남고, 함수 로직은 util 같은 모듈로 분리되게 되는걸까요?
created_at: 2025-03-07T02:37:58Z
author_association: COLLABORATOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: yjc2021
body: > 만약 2번 방식으로 적용을 하게 된다면 common.ts에는 type 만 남고, 함수 로직은 util 같은 모듈로 분리되게 되는걸까요?

네 그래야 할 것 같습니다. 연도를 예시로 들면 연도 값을 다룰 때 항상 createYear를 사용해야 한다는 것은 어떻게 보면 개발자가 숙지해야 하는 컨벤션을 하나 더 만드는 건데 이건 어쩔 수 없는 것일까요?
created_at: 2025-03-07T02:50:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: choi-ik
body: > 연도 값을 다룰 때 항상 createYear를 사용해야 한다는 것은 어떻게 보면 개발자가 숙지해야 하는 컨벤션을 하나 더 만드는 건데

이 부분도 조금 걸리네요.. 타입 하나를 강제하기 위해 `createYear`함수를 사용하도록 컨벤션을 하나 더 만드는 건 불필요한 컨벤션을 만드는 것 같다는 생각도 드는 것 같아요.

그럼 차라리 날짜 관련 타입을 string으로 사용해서 사용하는쪽에서 자유롭게 핸들링 할 수 있는 방향으로 갈까요? 이 부분은 용재님께서 어떻게 생각하실까요?
created_at: 2025-03-07T04:15:52Z
author_association: COLLABORATOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: yjc2021
body: > 그럼 차라리 날짜 관련 타입을 string으로 사용해서 사용하는쪽에서 자유롭게 핸들링 할 수 있는 방향

일단 string으로 진행하는게 가장 안정적일 것 같습니다
created_at: 2025-03-07T04:26:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,3 +30,56 @@ export type AvailablePtTime = {
   startTime: string;
   endTime: string;
 };
+
+// 날짜, 시간 포맷 (템플릿 리터럴 타입)
+type Year = `${number}${number}${number}${number}`;
+type Month = `${number}${number}`;
+type Day = `${number}${number}`;
+type Hour = `${number}${number}`;
+type Minute = `${number}${number}`;
path: packages/core/src/api/types/common.ts
user: login: choi-ik
body: 좋습니다 그럼 날짜데이터 타입을 모두 string으로 변경하도록 하겠습니다 👍
created_at: 2025-03-07T04:27:40Z
author_association: COLLABORATOR

PR comments:
user: login: yjc2021
created_at: 2025-03-07T02:33:56Z
body: > response 타입의 경우 실제로 api의 응답으로 주어지는 response는 ApiResponse라는 접미사를 붙혀주었고, ResponseBase타입에 제네릭으로 들어갈 타입은 Response 접미사만 붙혀서 작성하였습니다. 해당 네이밍도 이번 PR에서 컨벤션을 정하면 좋을 것 같아요.

그래도 진행하면 될 것 같습니다. 
- 다만, ApiResponse와 Response 사이 혼동이 발생할 가능성이 높아보이긴 하는데, Response 접미사를 붙힌 타입들도 export를 해야할까요? ApiResponse 접미사만 export하는 건 어떻게 생각하시나요? 만약 api 결과 data 타입이 필요하면 ApiResponse['data'] 이런 식으로 타입을 사용할 것 같네요
author_association: CONTRIBUTOR
user: login: choi-ik
created_at: 2025-03-07T02:41:16Z
body: @yjc2021 
> 다만, ApiResponse와 Response 사이 혼동이 발생할 가능성이 높아보이긴 하는데, Response 접미사를 붙힌 타입들도 export를 해야할까요? ApiResponse 접미사만 export하는 건 어떻게 생각하시나요? 만약 api 결과 data 타입이 필요하면 ApiResponse['data'] 이런 식으로 타입을 사용할 것 같네요

음 좋습니다. 저는 `만약 api 결과 data 타입이 필요하면 ApiResponse['data'] 이런 식으로 타입을 사용할 것 같네요` 이 의도로 data response 타입을 따로 분리한 것이었는데 용재님 방식처럼 사용해도 좋을 것 같아요.
그럼 추가로, 기존에 Api라는 네이밍을 붙힌 Response들에 Api 네이밍을 제거하는게 어떨까요?
author_association: COLLABORATOR
user: login: yjc2021
created_at: 2025-03-07T02:41:43Z
body: > API 함수도 편의를 위해 주석을 달아놓았습니다. 마찬가지로 Approve 이후 주석은 모두 제거할 예정입니다.
추가로, api 함수의 네이밍을 http 메소드(get/post/delete/patch/put)에 맞도록 접두사 컨벤션을 정하는게 좋을 것 같아요!

접두사가 필요하다고 느낀 이유가 있나요? 어떤 http 메서드를 api 요청 함수에서 사용하는 지 알 필요가 있을까라는 생각이긴 한데 필요하다면 접두사 추가하면 될 것 같습니다! 
접두사를 사용한다면 `GET,` `POST,` `PATCH,` `DELETE,` `PUT`로 하면 될까요?
author_association: CONTRIBUTOR
user: login: yjc2021
created_at: 2025-03-07T02:55:33Z
body: > @yjc2021
> 
> > 다만, ApiResponse와 Response 사이 혼동이 발생할 가능성이 높아보이긴 하는데, Response 접미사를 붙힌 타입들도 export를 해야할까요? ApiResponse 접미사만 export하는 건 어떻게 생각하시나요? 만약 api 결과 data 타입이 필요하면 ApiResponse['data'] 이런 식으로 타입을 사용할 것 같네요
> 
> 음 좋습니다. 저는 `만약 api 결과 data 타입이 필요하면 ApiResponse['data'] 이런 식으로 타입을 사용할 것 같네요` 이 의도로 data response 타입을 따로 분리한 것이었는데 용재님 방식처럼 사용해도 좋을 것 같아요. 그럼 추가로, 기존에 Api라는 네이밍을 붙힌 Response들에 Api 네이밍을 제거하는게 어떨까요?

그렇게 되면 기존 Response 접미사 type들의 네이밍과 중복되지 않나요? 네이밍 컨벤션 자체는 좋다고 생각하는데 타입을 import하는 상황에서 동일한 api 요청에 대해 Response와 ApiResponse 둘다 제공이 되면 혼동이 있을 거 같아서 ApiResponse만 export하고 Reponse 타입들은 .dto.ts 파일 안에서 놀게 하는 의도입니다 :)
author_association: CONTRIBUTOR
user: login: choi-ik
created_at: 2025-03-07T04:17:25Z
body: @yjc2021 
> 그렇게 되면 기존 Response 접미사 type들의 네이밍과 중복되지 않나요? 네이밍 컨벤션 자체는 좋다고 생각하는데 타입을 import하는 상황에서 동일한 api 요청에 대해 Response와 ApiResponse 둘다 제공이 되면 혼동이 있을 거 같아서 ApiResponse만 export하고 Reponse 타입들은 .dto.ts 파일 안에서 놀게 하는 의도입니다 :)

아 이해했습니다! 기존 response 타입을 제거하는 것이 아닌 export만 하지 말자는 의도였군요. 제가 글을 제대로 읽지 않았던 것 같아요. 용재님 피드백 반영하겠습니다 👍
author_association: COLLABORATOR
user: login: choi-ik
created_at: 2025-03-07T04:25:13Z
body: > 접두사가 필요하다고 느낀 이유가 있나요? 어떤 http 메서드를 api 요청 함수에서 사용하는 지 알 필요가 있을까라는 생각이긴 한데 필요하다면 접두사 추가하면 될 것 같습니다!
접두사를 사용한다면 GET, POST, PATCH, DELETE, PUT로 하면 될까요?

사실 http 메소드를 접두사로 꼭 써야한다기 보다는, '함수만 보고도 어떻게 데이터를 핸들링하는지 네이밍만 보고도 바로 알 수 있게 하자' 가 의도였습니다! 하지만 지금 생각해보니 API 명세서에 따라 자연스레 API 함수 네이밍을 `조회/삭제/수정/추가` 로 자연스레 작성하게 될 것 같네요?? 그래서 용재님과 동일하게 http 메소드 접두사를 꼭 붙히지 않아도 될 것 같다는 생각입니다.

author_association: COLLABORATOR
user: login: choi-ik
created_at: 2025-03-07T04:28:30Z
body: @yjc2021 
> params 접미사를 사용 시 path parameter와 query parameter 중 어떤 것인지 애매하다고 생각합니다. [queryParams, pathParams] 또는 [query, path]는 어떠신가요?

동의합니다 네이밍 반영할게요 👍
author_association: COLLABORATOR