diff --git a/src/components/domain/coupon/coupon-status-tag/index.tsx b/src/components/domain/coupon/coupon-status-tag/index.tsx
deleted file mode 100644
index 3667c1a6..00000000
--- a/src/components/domain/coupon/coupon-status-tag/index.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import styled from 'styled-components';
-import { CouponTagProps, StyledCouponTagProps } from './type';
-import { colors } from '@/constants/colors';
-import { TextBox } from '@components/atom/text-box';
-import {
- COUPON_STATUS_DISABLE,
- COUPON_STATUS_ENABLE,
- COUPON_STATUS_SOLD_OUT,
-} from '@/constants/coupon';
-
-export const CouponStatusTag = ({ status }: CouponTagProps) => {
- let borderColor = '';
- let backgroundColor = colors.primary;
- let color = colors.white;
- let text: string = COUPON_STATUS_ENABLE.label;
-
- if (status === COUPON_STATUS_SOLD_OUT.value) {
- borderColor = colors.orange;
- backgroundColor = colors.white;
- color = colors.orange;
- text = COUPON_STATUS_SOLD_OUT.label;
- }
-
- if (status === COUPON_STATUS_DISABLE.value) {
- backgroundColor = colors.black600;
- text = COUPON_STATUS_DISABLE.label;
- }
- return (
-
-
- {text}
-
-
- );
-};
-
-const StyledLayout = styled.div`
- width: 75px;
- height: 28px;
-
- background-color: ${(props) => props.backgroundColor};
- color: ${(props) => props.color};
- border: 1px solid ${(props) => props.borderColor || ''};
- border-radius: 2px;
-
- display: flex;
- align-items: center;
- justify-content: center;
-`;
diff --git a/src/components/domain/coupon/coupon-status-tag/type.ts b/src/components/domain/coupon/coupon-status-tag/type.ts
deleted file mode 100644
index 7ec53047..00000000
--- a/src/components/domain/coupon/coupon-status-tag/type.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export type StyledCouponTagProps = {
- backgroundColor: string;
- borderColor: string;
- color: string;
-};
-export type CouponTagProps = {
- status: string;
-};
diff --git a/src/components/domain/coupon/table-cell/index.tsx b/src/components/domain/coupon/table-cell/index.tsx
index 328746f0..670e52b5 100644
--- a/src/components/domain/coupon/table-cell/index.tsx
+++ b/src/components/domain/coupon/table-cell/index.tsx
@@ -2,11 +2,19 @@ import { TextBox } from '@components/atom/text-box';
import { Input, Tooltip } from 'antd';
import styled from 'styled-components';
import {
+ CouponTagProps,
+ StyledCouponTagProps,
couponNameContainerProps,
dayLimitInputProps,
roomContainerProps,
} from './type';
import { InfoCircleOutlined } from '@ant-design/icons';
+import { colors } from '@/constants/colors';
+import {
+ COUPON_STATUS_DISABLE,
+ COUPON_STATUS_ENABLE,
+ COUPON_STATUS_SOLD_OUT,
+} from '@/constants/coupon';
export const RoomContainer = ({ room }: roomContainerProps) => {
return (
@@ -115,3 +123,47 @@ const StyledDayLimitTitle = styled.div`
display: flex;
gap: 4px;
`;
+
+export const CouponStatusTag = ({ status }: CouponTagProps) => {
+ let borderColor = '';
+ let backgroundColor = colors.primary;
+ let color = colors.white;
+ let text: string = COUPON_STATUS_ENABLE.label;
+
+ if (status === COUPON_STATUS_SOLD_OUT.value) {
+ borderColor = colors.orange;
+ backgroundColor = colors.white;
+ color = colors.orange;
+ text = COUPON_STATUS_SOLD_OUT.label;
+ }
+
+ if (status === COUPON_STATUS_DISABLE.value) {
+ backgroundColor = colors.black600;
+ text = COUPON_STATUS_DISABLE.label;
+ }
+ return (
+
+
+ {text}
+
+
+ );
+};
+
+const StyledCouponStatusTag = styled.div`
+ width: 75px;
+ height: 28px;
+
+ background-color: ${(props) => props.backgroundColor};
+ color: ${(props) => props.color};
+ border: 1px solid ${(props) => props.borderColor || ''};
+ border-radius: 2px;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
diff --git a/src/components/domain/coupon/table-cell/type.ts b/src/components/domain/coupon/table-cell/type.ts
index c4195802..bb9a31c6 100644
--- a/src/components/domain/coupon/table-cell/type.ts
+++ b/src/components/domain/coupon/table-cell/type.ts
@@ -22,3 +22,11 @@ export type dayLimitInputProps = {
key: number,
) => void;
};
+export type StyledCouponTagProps = {
+ backgroundColor: string;
+ borderColor: string;
+ color: string;
+};
+export type CouponTagProps = {
+ status: string;
+};
diff --git a/src/components/domain/coupon/table/index.tsx b/src/components/domain/coupon/table/index.tsx
index cf5339d3..3de0f0cd 100644
--- a/src/components/domain/coupon/table/index.tsx
+++ b/src/components/domain/coupon/table/index.tsx
@@ -3,11 +3,11 @@ import Table, { ColumnsType } from 'antd/lib/table';
import styled from 'styled-components';
import {
CouponNameContainer,
+ CouponStatusTag,
DayLimitInput,
DayLimitTitle,
RoomContainer,
} from '../table-cell';
-import { CouponStatusTag } from '../coupon-status-tag';
import { Select } from 'antd';
import { TextBox } from '@components/atom/text-box';
import { TableProps, couponTableProps, tableData } from './type';
diff --git a/src/hooks/coupon/useCoupon.ts b/src/hooks/coupon/useCoupon.ts
index 862c046d..d788283d 100644
--- a/src/hooks/coupon/useCoupon.ts
+++ b/src/hooks/coupon/useCoupon.ts
@@ -25,17 +25,29 @@ import { isCouponModifiedState } from '@stores/coupon/atom';
* @description 쿠폰 관리 페이지 로직을 다루는 hook
*
* @returns
- * data,
- isGetCouponError,
- deleteCoupon,
+ deleteCoupon,
couponData,
handleSelectStatus,
handleSelectRecord,
handleSelectCouponType,
handleChangeDayLimit,
handleDeleteButton,
- isModified,
handleChangeDate,
+ handleEditButton,
+ handleModalOpen,
+ handleModalClose,
+ isModalOpen,
+ handleBatchEditCheckbox,
+ purchaseData,
+ handleChangeBatchValue,
+ handleChangeBuyQuantity,
+ handlePurchaseButton,
+ isPointModalOpen,
+ setIsPointModalOpen,
+ isGetCouponLoading,
+ handleAgreeCheckbox,
+ isAgreed,
+ error,
*/
export const useCoupon = () => {
@@ -111,9 +123,9 @@ export const useCoupon = () => {
className: 'confirm-modal',
onOk: () => setIsPointModalOpen(true),
});
- } else {
- message.error('요청에 실패했습니다. 잠시 후 다시 시도해 주세요.');
+ return;
}
+ message.error('요청에 실패했습니다. 잠시 후 다시 시도해 주세요.');
},
});
@@ -164,37 +176,29 @@ export const useCoupon = () => {
};
}, []);
+ /**
+ * 서버로부터 받은 쿠폰 데이터를 테이블에 할당할 수 있는 데이터로 가공
+ * @param {Coupons} data 서버로 부터 받은 쿠폰 데이터
+ */
const processCouponTableData = (data: Coupons) => {
- const couponTableData = [];
- const originData = [];
+ const couponTableData = createData(data);
+ const originData = createData(data);
+ setCouponData({ expiry: data.expiry, coupons: [...couponTableData] });
+ originCouponTableData.current = {
+ expiry: data.expiry,
+ coupons: [...originData],
+ };
+ };
+
+ const createData = (data: Coupons) => {
+ const resultData = [];
let key = -1;
for (const room of data.rooms) {
for (let index = 0; index < room.coupons.length; index++) {
key++;
const coupon = room.coupons[index];
const length = index === 0 ? room.coupons.length : 0;
- couponTableData.push({
- room: {
- name: room.roomName,
- price: room.roomPrice,
- id: room.roomId,
- length,
- },
- key,
- couponId: coupon.couponId,
- status: coupon.status,
- info: {
- name: coupon.couponName,
- appliedPrice: coupon.appliedPrice,
- },
- dayLimit: coupon.dayLimit,
- quantity: coupon.quantity,
- couponType: coupon.couponType,
- discount: coupon.discount,
- discountType: coupon.discountType,
- isSoldOut: coupon.status === 'SOLD_OUT',
- });
- originData.push({
+ resultData.push({
room: {
name: room.roomName,
price: room.roomPrice,
@@ -217,13 +221,11 @@ export const useCoupon = () => {
});
}
}
- setCouponData({ expiry: data.expiry, coupons: [...couponTableData] });
- originCouponTableData.current = {
- expiry: data.expiry,
- coupons: [...originData],
- };
+ return resultData;
};
-
+ /**
+ * 서버로부터 받은 쿠폰 데이터를 추가 구매 모달에 출력될 수 있는 데이터로 가공
+ */
const processPurchaseData = () => {
const data: PurchaseData = {
batchValue: 0,
@@ -266,6 +268,11 @@ export const useCoupon = () => {
setPurchaseData(data);
};
+ /**
+ * 쿠폰 상태 변경시 check 된 아이템의 쿠폰 상태를 변경
+ * @param {string} value 변경된 쿠폰 상태
+ */
+
const handleSelectStatus = (value: string) => {
setSelectedStatus(value);
const { expiry, coupons: data } = { ...couponData };
@@ -275,6 +282,12 @@ export const useCoupon = () => {
setCouponData({ expiry, coupons: data });
};
+ /**
+ * checkbox를 통해 쿠폰 아이템 선택 시 selectedRowKey에 해당 쿠폰 아이템 key를 추가하고
+ * 쿠폰 아이템의 상태를 select box에 있는 상태로 변경시켜주는 함수
+ * @param {number} selectedRowKeys 선택된 record keys
+ */
+
const handleSelectRecord = (selectedRowKeys: number[]) => {
const { expiry, coupons: data } = { ...couponData };
selectedRowKeys.map((key) => {
@@ -286,12 +299,24 @@ export const useCoupon = () => {
setSelectedRowKeys(selectedRowKeys);
};
+ /**
+ * 노출 기준 선택 시 couponData state를 업데이트 시켜주는 함수
+ * @param {string} value 선택된 노출 기준 값
+ * @param {number} key 선택된 쿠폰 아이템
+ */
+
const handleSelectCouponType = (value: string, key: number) => {
const { expiry, coupons: data } = { ...couponData };
data[key].couponType = value;
setCouponData({ expiry, coupons: data });
};
+ /**
+ * 일일 제한 수량 input 값 변경 시 couponData state를 업데이트 시켜주는 함수
+ * @param {React.ChangeEvent} event 발생한 이벤트
+ * @param {number} key 선택된 쿠폰 아이템
+ */
+
const handleChangeDayLimit = (
event: React.ChangeEvent,
key: number,
@@ -304,15 +329,29 @@ export const useCoupon = () => {
setCouponData({ expiry, coupons: data });
};
+ /**
+ * 쿠폰 적용 기간 변경 시 couponData state를 업데이트 시켜주는 함수
+ * @param {string} date 날짜
+ */
+
const handleChangeDate = (date: string) => {
const { coupons } = { ...couponData };
setCouponData({ expiry: date, coupons });
};
+ /**
+ * checkbox로 선택된 row가 존재하는지 확인하는 함수
+ * @returns {boolean} 존재 여부
+ */
const isSelectedRow = () => {
return selectedRowKeys.length !== 0;
};
+ /**
+ * 선택된 row 중 쿠폰 상태가 소진인 아이템이 존재하는지 확인하는 함수
+ * @param {number[]} selectedRowKeys
+ * @returns {boolean} 존재 여부
+ */
const findNotSoldOutData = (selectedRowKeys: number[]) => {
for (let index = 0; index < selectedRowKeys.length; index++) {
const key = selectedRowKeys[index];
@@ -321,35 +360,56 @@ export const useCoupon = () => {
return false;
};
+ /**
+ * 삭제할 데이터를 서버에게 request 하기 위해 가공하는 함수
+ * @param {number[]} selectedRowKeys 선택된 rows의 key
+ */
const processDeleteData = (selectedRowKeys: number[]) => {
- const rooms: { couponId: number }[][] = [];
- for (let index = 0; index < selectedRowKeys.length; index++) {
- const key = selectedRowKeys[index];
+ const roomsMap = createDeleteRoomsMap(selectedRowKeys);
+ const data = createDeleteParams(roomsMap);
+ return data;
+ };
+
+ const createDeleteRoomsMap = (selectedRowKeys: number[]) => {
+ const roomsMap = new Map();
+ for (const key of selectedRowKeys) {
const { room, couponId } = couponData.coupons[key];
- if (!rooms[room.id]) {
- rooms[room.id] = [];
- }
- rooms[room.id].push({ couponId });
+ const roomCoupons = roomsMap.get(room.id) || [];
+ roomCoupons.push({ couponId });
+ roomsMap.set(room.id, roomCoupons);
}
+ return roomsMap;
+ };
+
+ const createDeleteParams = (
+ roomsMap: Map,
+ ) => {
const data: CouponDeleteParams = {
accommodationId: Number(accommodationId as string),
rooms: [],
};
- for (let index = 0; index < rooms.length; index++) {
- if (rooms[index]) {
- const roomsData = {
- roomId: index,
- coupons: rooms[index],
- };
- data.rooms.push(roomsData);
- }
- }
+ roomsMap.forEach((roomCoupons, roomId) => {
+ const roomsData = {
+ roomId,
+ coupons: roomCoupons,
+ };
+ data.rooms.push(roomsData);
+ });
return data;
};
+ /**
+ * 수정할 쿠폰 데이터를 서버에게 request 하기 위해 가공하는 함수
+ */
const processEditData = () => {
- const rooms: EditCoupon[][] = [];
- for (let index = 0; index < couponData.coupons.length; index++) {
+ const roomsMap = createEditRoomsMap();
+ const data = createEditParams(roomsMap);
+ return data;
+ };
+
+ const createEditRoomsMap = () => {
+ const roomsMap = new Map();
+ for (const coupon of couponData.coupons) {
const {
room,
couponId,
@@ -358,11 +418,9 @@ export const useCoupon = () => {
discountType,
dayLimit,
couponType,
- } = couponData.coupons[index];
- if (!rooms[room.id]) {
- rooms[room.id] = [];
- }
- rooms[room.id].push({
+ } = coupon;
+ const roomCoupons = roomsMap.get(room.id) || [];
+ roomCoupons.push({
couponId,
status,
discount,
@@ -370,23 +428,29 @@ export const useCoupon = () => {
dayLimit,
couponType,
});
+ roomsMap.set(room.id, roomCoupons);
}
+ return roomsMap;
+ };
+
+ const createEditParams = (roomsMap: Map) => {
const data: CouponEditParams = {
accommodationId: Number(accommodationId as string),
expiry: couponData.expiry,
rooms: [],
};
- for (let index = 0; index < rooms.length; index++) {
- if (rooms[index]) {
- data.rooms.push({
- roomId: index,
- coupons: rooms[index],
- });
- }
- }
+ roomsMap.forEach((roomCoupons, roomId) => {
+ data.rooms.push({
+ roomId,
+ coupons: roomCoupons,
+ });
+ });
return data;
};
+ /**
+ * 삭제 버튼을 클릭했을 때 실행할 함수
+ */
const handleDeleteButton = () => {
if (isCouponModified) {
message.warning('수정 중인 내용을 먼저 저장하세요');
@@ -421,6 +485,9 @@ export const useCoupon = () => {
});
};
+ /**
+ * 저장 버튼을 클릭했을 때 실행할 함수
+ */
const handleEditButton = () => {
Modal.confirm({
title:
@@ -435,6 +502,10 @@ export const useCoupon = () => {
});
};
+ /**
+ * 추가 구매 버튼을 클릭했을 때 실행할 함수
+ */
+
const handleModalOpen = () => {
if (isCouponModified) {
message.warning('수정 중인 내용을 먼저 저장하세요');
@@ -451,18 +522,32 @@ export const useCoupon = () => {
setIsModalOpen(false);
};
+ /**
+ * 개별 구매 수량 input 값의 유효성 검사를 하는 함수
+ * @param {number} value
+ * @param {PurchaseCoupons} coupon
+ */
const validateBuyQuantity = (value: number, coupon: PurchaseCoupons) => {
if (value > 999 || value < 0) return;
if (Number.isNaN(value)) coupon.buyQuantity = 0;
else coupon.buyQuantity = value;
};
+ /**
+ * 일괄 적용 input 값의 유효성 검사를 하는 함수
+ * @param {number} value
+ * @param {PurchaseData} data
+ */
const validateBatchValue = (value: number, data: PurchaseData) => {
if (value > 999 || value < 0) return;
if (Number.isNaN(value)) data.batchValue = 0;
else data.batchValue = value;
};
+ /**
+ * 일괄 적용 input 값이 업데이트 되었을 때 실행할 함수
+ * @param {PurchaseData} data
+ */
const handleBatchUpdate = (data: PurchaseData) => {
for (const room of data.rooms) {
if (!room) continue;
@@ -475,6 +560,9 @@ export const useCoupon = () => {
setPurchaseData(data);
};
+ /**
+ * 일괄 적용 checkbox의 값을 변경했을 때 실행할 함수
+ */
const handleBatchEditCheckbox = () => {
if (!purchaseData) return;
const data = { ...purchaseData };
@@ -484,6 +572,10 @@ export const useCoupon = () => {
handleBatchUpdate(data);
};
+ /**
+ *일괄 적용 input 값이 업데이트 되었을 때 실행할 함수
+ * @param {React.ChangeEvent} event
+ */
const handleChangeBatchValue = (
event: React.ChangeEvent,
) => {
@@ -494,6 +586,12 @@ export const useCoupon = () => {
handleBatchUpdate(data);
};
+ /**
+ * 개별 구매 수량 input 값이 업데이트 되었을 때 실행할 함수
+ * @param {React.ChangeEvent} event
+ * @param {number} couponId
+ * @param {number} roomId
+ */
const handleChangeBuyQuantity = (
event: React.ChangeEvent,
couponId: number,
@@ -515,6 +613,10 @@ export const useCoupon = () => {
setPurchaseData(data);
};
+ /**
+ * 추가 구매 시 서버에게 request 보낼 데이터를 가공하는 함수
+ */
+
const processPurchasePostData = () => {
const data: PurchaseCouponParams = {
accommodationId: Number(accommodationId as string),
@@ -553,6 +655,10 @@ export const useCoupon = () => {
data.rooms = roomData;
return data;
};
+
+ /**
+ * 구매하기 버튼 클릭 시 실행할 함수
+ */
const handlePurchaseButton = () => {
Modal.confirm({
content: '쿠폰을 구매하시겠습니까?',
diff --git a/src/test/coupon/Coupon.test.tsx b/src/test/coupon/Coupon.test.tsx
index bf5d105f..a6d46404 100644
--- a/src/test/coupon/Coupon.test.tsx
+++ b/src/test/coupon/Coupon.test.tsx
@@ -3,6 +3,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Coupon } from '@pages/coupon';
+import { RecoilRoot } from 'recoil';
jest.mock('antd/es/locale/ko_KR', () => ({
locale: () => null,
@@ -13,21 +14,25 @@ describe('쿠폰 데이터', () => {
/* 기본 동작 */
test('쿠폰 페이지가 렌더링된다', async () => {
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('coupon-header'));
});
test('쿠폰 아이템 체크박스가 작동한다', () => {
const { container } = render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
const checkbox = container.querySelectorAll(
"input[type='checkbox']",
@@ -40,11 +45,13 @@ describe('쿠폰 데이터', () => {
/* 삭제 */
test('아무것도 선택되지 않았을 때, 선택 삭제를 클릭하면 삭제할 쿠폰을 먼저 선택하세요 라는 메세지가 출력된다', async () => {
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
timeout: 5000,
@@ -60,11 +67,13 @@ describe('쿠폰 데이터', () => {
test('수정 중 선택 삭제 버튼을 클릭하면 수정 중인 내용을 먼저 저장하세요 라는 메시지가 출력된다', async () => {
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
@@ -86,11 +95,13 @@ describe('쿠폰 데이터', () => {
test('수량이 남아있는 쿠폰 삭제 시 수량이 남아있는 쿠폰이 있습니다 라는 메시지가 출력된다', async () => {
const { container } = render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
@@ -120,11 +131,13 @@ describe('쿠폰 데이터', () => {
/* 수정 */
test('쿠폰 아이템 일일 제한 수량 수정 후 저장버튼 활성화된다.', () => {
const { container } = render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
const inputBox = container.querySelectorAll(
@@ -140,11 +153,13 @@ describe('쿠폰 데이터', () => {
});
test('쿠폰 아이템 일일 제한 수량에 문자 입력 시 block 된다', async () => {
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
@@ -160,11 +175,13 @@ describe('쿠폰 데이터', () => {
test('저장 버튼을 클릭하면 저장하시겠습니까? 라는 문구가 출력된다', async () => {
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
@@ -192,11 +209,13 @@ describe('쿠폰 데이터', () => {
test('아무것도 선택되지 않았을 때, 추가 구매를 클릭하면 삭제할 쿠폰을 먼저 선택하세요 라는 메세지가 출력된다', async () => {
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
timeout: 5000,
@@ -212,11 +231,13 @@ describe('쿠폰 데이터', () => {
test('쿠폰 아이템 선택 후 추가 구매 버튼 클릭 시 추가 구매 모달이 출력된다', async () => {
const { container } = render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
@@ -236,11 +257,13 @@ describe('쿠폰 데이터', () => {
});
test('수량 일괄 적용 클릭 시 수량 일괄 적용 input 값이 구매 쿠폰 input에 적용된다', async () => {
const { container } = render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {
@@ -269,11 +292,13 @@ describe('쿠폰 데이터', () => {
});
test('추가 구매 input에 문자 입력 시 block 된다', async () => {
const { container } = render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
);
await waitFor(() => screen.findByTestId('table-container'), {