Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[혜택관리] 혜택 문구 추가, 수정 기능 추가 #72

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/model/benefit.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ export interface BenefitCategoryContent {

export interface GetBenefitShopsResponse {
count: number;
shops: Shops[];
shops: ShopInfo[];
}

export interface Shops {
id: number;
name: string;
}

export interface ShopInfo extends Shops {
shop_benefit_map_id: number;
detail: string;
}

export interface SearchResponse {
benefit_shops: Shops[];
non_benefit_shops: Shops[]
Expand All @@ -41,11 +46,26 @@ export interface DeleteShopsRequest {
id: number;
}

export interface AddShopRequest extends DeleteShopsRequest {}
export interface ShopDetail {
shop_id: number;
detail: string;
}

export interface AddShopRequest {
id: number;
shop_details: ShopDetail[];
}

export interface ModifyBenefitRequest {
body: CreateBenefitRequest;
id: number;
}

export interface ModifyBenefitDetails {
modify_details: {
shop_benefit_map_id: number;
detail: string;
}[];
}

export interface ModifyBenefitForm extends CreateBenefitRequest { }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 검토 결과, 다음과 같은 주요 사항과 개선점을 확인했습니다:

  1. 인터페이스 이름 통일성: ShopsShopInfo의 관계를 명확히 하기 위해 ShopInfoShops를 확장하는 형태로 수정되었습니다. 이는 데이터의 의미를 명확히 하고 혼동을 줄일 수 있습니다.
  2. AddShopRequest의 구조 개선: AddShopRequest에서 DeleteShopsRequest를 상속받지 않는 것을 권장합니다. 대신 새로운 필드인 shop_details를 추가하여 이 요청의 목적을 더 분명하게 나타냅니다.

아래는 개선이 필요한 부분의 diff 형식입니다:

-  shops: Shops[];
+  shops: ShopInfo[];

 export interface Shops {
   id: number;
   name: string;
 }
 
+export interface ShopInfo extends Shops {
+  shop_benefit_map_id: number;
+  detail: string;
+}

-export interface AddShopRequest extends DeleteShopsRequest {}
+export interface AddShopRequest {
+  id: number;
+  shop_details: ShopDetail[];
+}

이와 같은 개선 사항을 통해 코드의 가독성과 유지보수성을 높일 수 있으며, 각 데이터 구조에 대한 명확한 이해를 도울 수 있습니다.

Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,6 @@ export const SearchWrapper = styled.div`
position: relative;
`;

export const SelectContainer = styled.div`
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: repeat(auto-fill, 50px);
place-items: center;
width: 100%;
height: 600px;
overflow: auto;
padding: 25px;
border: 0.5px solid #eeeeeeff;
background: #eee;
`;

export const FlexColumn = styled.div`
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -54,12 +41,12 @@ export const ButtonContent = styled.span`
`;

export const ButtonWrapper = styled.div`
display: relative;
position: relative;
`;

export const DeleteButtonWrapper = styled.button`
position: absolute;
top: -10px;
top: -15px;
left: -10px;
background: none;
border: none;
Expand All @@ -72,3 +59,10 @@ export const FlexRight = styled.div`
justify-content: flex-end;
margin-top: 10px
`;

export const DetailInput = styled.input`
width: 100%;
height: 100%;
border: none;
outline: none;
`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 리뷰 결과에 따라 몇 가지 개선 사항을 제안합니다.

  1. 주석 추가: SelectContainer가 주석처리된 이유가 명확하지 않습니다. 이 부분에 대한 설명이 필요할 수 있습니다.

  2. CSS 속성을 사용하는 방식: ButtonWrapperdisplay 속성을 relative로 수정하는 대신 position 속성을 사용한 것은 올바른 절차입니다. 그러나 CSS 속성에서 display: relative는 존재하지 않으므로 이를 position: relative로 수정한 것은 좋은 개선입니다.

  3. 부정확한 위치값: DeleteButtonWrappertop 값을 -10px에서 -15px으로 조정한 것은 구체적인 목표에 따라 차이가 있을 수 있지만, 이 변경이 적절한지에 대한 주석이 필요할 것 같습니다.

  4. 입력 필드 스타일링: DetailInput에 대한 스타일이 새로 추가되었습니다. 이 스타일은 사용에 적합해 보이나, 해당 입력 필드가 어디에 사용될지에 대한 설명이 주석으로 추가될 수 있다면 더욱 좋을 것입니다.

다음은 개선이 필요한 부분에 대한 diff 형식 제안입니다:

@@ -1,6 +1,7 @@
 /*
- SelectContainer는 현재 사용되지 않습니다. 필요에 따라 다시 활성화할 수 있습니다.
+ SelectContainer가 주석처리된 이유에 대한 설명이 필요합니다.
 */
-export const SelectContainer = styled.div`
+// export const SelectContainer = styled.div`
 
@@ -54,12 +55,12 @@ export const ButtonContent = styled.span`
-  display: relative; 
+  position: relative; // display 속성 대신 position 속성 사용
 `;
 
 export const DeleteButtonWrapper = styled.button`
   position: absolute;
-  top: -10px;
+  top: -15px; // 위치 조정 필요성에 대한 설명 추가
   left: -10px;
   background: none;
   border: none;
@@ -72,3 +73,10 @@ export const FlexRight = styled.div`
   margin-top: 10px;
 `;
 
+// DetailInput은 특정 폼에 사용될 입력 필드입니다.
 export const DetailInput = styled.input`
   width: 100%;
   height: 100%;
   border: none;
   outline: none;
 `;

각 수정 사항에 주석을 추가하여 코드의 맥락을 명확히 할 수 있도록 했습니다.

70 changes: 54 additions & 16 deletions src/pages/Services/Benefit/components/AdditionalModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,25 @@ import { useAddBenefitShopsMutation, useSearchShopsQuery } from 'store/api/benef
import { Shops } from 'model/benefit.model';
import { MinusCircleOutlined, UploadOutlined } from '@ant-design/icons';
import * as S from './index.style';
// eslint-disable-next-line
import * as Style from '../../index.style';

interface Props {
id: number | undefined;
closeAdditionModal: () => void;
}

interface ShopDetail {
shop_id: number;
detail: string;
}

const { Search } = Input;
export default function AdditionalModal({ id, closeAdditionModal }: Props) {
const [keyword, setKeyword] = useState<string>('');
const [isFocus, setIsFocus] = useState(false);
const [shops, setShops] = useState<Shops[]>([]);
const [details, setDetails] = useState<ShopDetail[]>([]);
const searchRef = useRef<InputRef>(null);
const userInput = (e: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value);
Expand All @@ -33,29 +41,48 @@ export default function AdditionalModal({ id, closeAdditionModal }: Props) {
const addShop = (shopId: number, name: string) => {
if (shops.find((shop) => shop.id === shopId)) return;
setShops((prev) => [...prev, { id: shopId, name }]);
setDetails((prev) => [...prev, { shop_id: shopId, detail: '' }]);
setKeyword('');
};
const cancelAddShop = (shopId: number) => {
const filteredShop = shops.filter((shop) => shop.id !== shopId);
const filteredDetail = details.filter((shop) => shop.shop_id !== shopId);
setShops(filteredShop);
setDetails(filteredDetail);
};

const ConfirmAddShop = async () => {
if (shops.length === 0) {
closeAdditionModal();
return;
}
if (id) {
const requestBody = shops.map((shop) => shop.id);
await addShopMutation({ id, shop_ids: requestBody })
if (details.some((shop) => shop.detail === '')) {
message.error('상세정보를 입력해주세요.');
return;
}
await addShopMutation({ id, shop_details: details })
.then(() => {
message.success('상점을 추가했습니다.');
setShops([]);
setDetails([]);
setKeyword('');
closeAdditionModal();
});
}
};

const handleDetail = (e: React.ChangeEvent<HTMLInputElement>, shopId: number) => {
const { value } = e.target;
const shopDetail = details.find((shop) => shop.shop_id === shopId);
if (shopDetail) {
const filteredDetail = details.filter((shop) => shop.shop_id !== shopId);
setDetails([...filteredDetail, { shop_id: shopId, detail: value }]);
} else {
setDetails((prev) => [...prev, { shop_id: shopId, detail: value }]);
}
};

if (isError) message.error('상점을 추가할 수 없습니다.');

return (
Expand Down Expand Up @@ -87,20 +114,31 @@ export default function AdditionalModal({ id, closeAdditionModal }: Props) {
</S.SearchWrapper>

<Divider orientation="left">선택 상점</Divider>
<S.SelectContainer>
{shops.map((shop) => (
<S.ButtonWrapper key={shop.id}>
<Button style={{ width: '180px' }}>
<S.DeleteButtonWrapper onClick={() => cancelAddShop(shop.id)}>
<MinusCircleOutlined />
</S.DeleteButtonWrapper>
<S.ButtonContent>
{shop.name}
</S.ButtonContent>
</Button>
</S.ButtonWrapper>
))}
</S.SelectContainer>
<Style.ShopList>
<thead>
<Style.HeaderRow>
<Style.HeaderItem>상점명</Style.HeaderItem>
<Style.HeaderItem>상세정보</Style.HeaderItem>
</Style.HeaderRow>
</thead>
<tbody>
{shops.map((shop) => (
<Style.Row key={shop.id} isclicked={false}>
<Style.TitleItem>
<S.ButtonWrapper key={shop.id}>
<S.DeleteButtonWrapper onClick={() => cancelAddShop(shop.id)}>
<MinusCircleOutlined />
</S.DeleteButtonWrapper>
{shop.name}
</S.ButtonWrapper>
</Style.TitleItem>
<Style.DetailItem>
<S.DetailInput onChange={(e) => handleDetail(e, shop.id)} />
</Style.DetailItem>
</Style.Row>
))}
</tbody>
</Style.ShopList>
<S.FlexRight>
<Button
onClick={ConfirmAddShop}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import {
Divider, Button, message,
} from 'antd';
import { MinusCircleOutlined, UploadOutlined } from '@ant-design/icons';
import * as S from 'pages/Services/Benefit/components/AdditionalModal/index.style';
import { ShopInfo } from 'model/benefit.model';
import { useEffect, useState } from 'react';
import { useModifyBenefitDetailsMutation } from 'store/api/benefit';
import * as Style from 'pages/Services/Benefit/index.style';

interface Props {
closeBenefitModifyModal: () => void;
shops?: ShopInfo[];
}

interface ModifyDetails {
shop_id: number;
detail: string;
name: string;
shop_benefit_map_id: number;
}

export default function BenefitDetailModifyModal({ closeBenefitModifyModal, shops }: Props) {
const [details, setDetails] = useState<ModifyDetails[]>([]);
const [mutation] = useModifyBenefitDetailsMutation();

const handleDetail = (e: React.ChangeEvent<HTMLInputElement>, shopId: number) => {
const { value } = e.target;
const shopDetail = details.find((shop) => shop.shop_id === shopId);
if (shopDetail) {
const findItem = details.find((shop) => shop.shop_id === shopId);
if (!findItem) return;
const newDetail = {
Comment on lines +29 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좀 더 깔끔하게 어떻게하면 좋을까요?

shop_id: findItem.shop_id,
detail: value,
name: findItem.name,
shop_benefit_map_id: findItem.shop_benefit_map_id,
};
const filteredDetail = details.filter((shop) => shop.shop_id !== shopId);
setDetails([...filteredDetail, newDetail]);
} else {
setDetails((prev) => [...prev, {
shop_id: shopId,
detail: value,
name: shops?.find((shop) => shop.id === shopId)?.name || '',
shop_benefit_map_id: shops?.find((shop) => shop.id === shopId)?.shop_benefit_map_id || 0,
}]);
}
};

const modifyDetails = () => {
const shopDetails = details.map((shop) => ({
shop_benefit_map_id: shop.shop_benefit_map_id,
detail: shop.detail,
}));
mutation({ modify_details: shopDetails });
};

const confirmModifyDetails = () => {
if (details.some((shop) => shop.detail.trim() === '')) {
message.error('상세정보를 입력해주세요.');
return;
}
modifyDetails();
closeBenefitModifyModal();
};

useEffect(() => {
if (shops) {
const shopDetails = shops.map((shop) => ({
shop_id: shop.id,
detail: shop.detail,
name: shop.name,
shop_benefit_map_id: shop.shop_benefit_map_id,
}));
setDetails(shopDetails);
}
}, [shops]);

if (!shops) return null;

return (
<div>
<Divider orientation="left">선택 상점</Divider>
<Style.ShopList>
<thead>
<Style.HeaderRow>
<Style.HeaderItem>상점명</Style.HeaderItem>
<Style.HeaderItem>상세정보</Style.HeaderItem>
</Style.HeaderRow>
</thead>
<tbody>
{details.map((shop) => (
<Style.Row key={shop.shop_id} isclicked={false}>
<Style.TitleItem>
<S.ButtonWrapper key={shop.shop_id}>
<S.DeleteButtonWrapper>
<MinusCircleOutlined />
</S.DeleteButtonWrapper>
{shop.name}
</S.ButtonWrapper>
</Style.TitleItem>
<Style.DetailItem>
<S.DetailInput
onChange={(e) => handleDetail(e, shop.shop_id)}
value={details.find((detail) => detail.shop_id === shop.shop_id)?.detail}
/>
</Style.DetailItem>
</Style.Row>
))}
</tbody>
</Style.ShopList>
<S.FlexRight>
<Button
onClick={confirmModifyDetails}
>
<UploadOutlined />
완료
</Button>
</S.FlexRight>
</div>
);
}
Loading
Loading