diff --git a/src/model/benefit.model.ts b/src/model/benefit.model.ts index 78c84517..fba0d7c4 100644 --- a/src/model/benefit.model.ts +++ b/src/model/benefit.model.ts @@ -12,7 +12,7 @@ export interface BenefitCategoryContent { export interface GetBenefitShopsResponse { count: number; - shops: Shops[]; + shops: ShopInfo[]; } export interface Shops { @@ -20,6 +20,11 @@ export interface Shops { name: string; } +export interface ShopInfo extends Shops { + shop_benefit_map_id: number; + detail: string; +} + export interface SearchResponse { benefit_shops: Shops[]; non_benefit_shops: Shops[] @@ -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 { } diff --git a/src/pages/Services/Benefit/components/AdditionalModal/index.style.ts b/src/pages/Services/Benefit/components/AdditionalModal/index.style.ts index 2fb1146e..ff75c1d1 100644 --- a/src/pages/Services/Benefit/components/AdditionalModal/index.style.ts +++ b/src/pages/Services/Benefit/components/AdditionalModal/index.style.ts @@ -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; @@ -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; @@ -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; +`; diff --git a/src/pages/Services/Benefit/components/AdditionalModal/index.tsx b/src/pages/Services/Benefit/components/AdditionalModal/index.tsx index 09c1fd66..e6f59ee1 100644 --- a/src/pages/Services/Benefit/components/AdditionalModal/index.tsx +++ b/src/pages/Services/Benefit/components/AdditionalModal/index.tsx @@ -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(''); const [isFocus, setIsFocus] = useState(false); const [shops, setShops] = useState([]); + const [details, setDetails] = useState([]); const searchRef = useRef(null); const userInput = (e: React.ChangeEvent) => { setKeyword(e.target.value); @@ -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, 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 ( @@ -87,20 +114,31 @@ export default function AdditionalModal({ id, closeAdditionModal }: Props) { 선택 상점 - - {shops.map((shop) => ( - - - - ))} - + + + + 상점명 + 상세정보 + + + + {shops.map((shop) => ( + + + + cancelAddShop(shop.id)}> + + + {shop.name} + + + + handleDetail(e, shop.id)} /> + + + ))} + + + + + ); +} diff --git a/src/pages/Services/Benefit/index.style.tsx b/src/pages/Services/Benefit/index.style.tsx index dd9f3d30..f411c5c0 100644 --- a/src/pages/Services/Benefit/index.style.tsx +++ b/src/pages/Services/Benefit/index.style.tsx @@ -6,6 +6,7 @@ export const Container = styled.div` display: flex; align-items: center; flex-direction: column; + min-width: 1500px; `; export const SideContainer = styled.div` @@ -33,32 +34,39 @@ export const Wrapper = styled.div` `; -export const ShopContainer = styled.div` +export const Row = styled.tr<{ isclicked: boolean }>` display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; - grid-template-rows: repeat(auto-fill, 80px); - place-items: center; + grid-template-columns: 40% 60%; + cursor: pointer; + width: 100%; + height: 50px; + border: ${(props) => (props.isclicked ? '1px solid rgba(129, 173, 255, 0.8) ' : '1px solid #f0f0f0')}; `; -export const Button = styled.button<{ isclicked: boolean }>` - cursor: pointer; - width: 150px; +export const HeaderRow = styled.tr` + display: grid; + grid-template-columns: 40% 60%; + width: 100%; height: 50px; - background: #fff; - text-align: center; - transition: scale 0.2s; - border: ${(props) => (props.isclicked ? '4px solid rgba(129, 173, 255, 0.8) ' : 'none')}; - border-radius: 10px; + border: 1px solid #f0f0f0; +`; + +export const HeaderItem = styled.th` + padding: 15px; + border-right: 1px solid #f0f0f0; +`; + +export const TitleItem = styled.td` + padding: 15px; + border-right: 1px solid #f0f0f0; +`; - &:active { - scale: 0.95; - } +export const DetailItem = styled.td` + padding: 15px; `; -export const ShopListContainer = styled.div` - background: #ddd; - padding: 25px; - height: 60%; +export const ShopList = styled.table` width: 100%; - overflow-y: auto; + border-spacing: 0 10px; + border-collapse: collapse; `; diff --git a/src/pages/Services/Benefit/index.tsx b/src/pages/Services/Benefit/index.tsx index 49d7fe64..525075bf 100644 --- a/src/pages/Services/Benefit/index.tsx +++ b/src/pages/Services/Benefit/index.tsx @@ -9,6 +9,7 @@ import AdditionalModal from './components/AdditionalModal'; import CreationModal from './components/CreationModal'; import DeleteBenefitCategoryModal from './components/DeleteBenefitCategoryModal'; import ModifyModal from './components/ModifyModal'; +import BenefitDetailModifyModal from './components/BenefitDetailModifyModal'; export default function BenefitPage() { const [selected, setSelected] = useState(); @@ -20,6 +21,7 @@ export default function BenefitPage() { const [isCreateOpen, setIsCreateOpen] = useState(false); const [isDeleteOpne, setIsDeleteOpen] = useState(false); const [isModifyOpen, setIsModifyOpen] = useState(false); + const [isBenefitDetailOpen, setIsBenefitDetailOpen] = useState(false); const [selectedShop, setSelectedShop] = useState([]); const onShopClick = (id: number) => { setSelectedShop((prev) => { @@ -46,29 +48,18 @@ export default function BenefitPage() { setSelectedShop([...allId]); } }; - const openAddtionModal = () => { - setIsAdditionOpen(true); - }; - const closeAdditionModal = () => { - setIsAdditionOpen(false); - }; - const openCreateModal = () => { - setIsCreateOpen(true); - }; - const closeCreateModal = () => { - setIsCreateOpen(false); - }; - const openDeleteModal = () => { - if (selected) setIsDeleteOpen(true); - }; - const closeDeleteModal = () => { - setIsDeleteOpen(false); - }; - const openModifyModal = () => { - setIsModifyOpen(true); - }; - const closeModifyModal = () => { - setIsModifyOpen(false); + + const handleModal = (setOpenModal: React.Dispatch>, type: string, state: 'open' | 'close') => { + if (type === 'delete' && state === 'open') { + if (selected) setOpenModal(true); + return; + } + + if (state === 'open') { + setOpenModal(true); + } else { + setOpenModal(false); + } }; return ( @@ -88,13 +79,13 @@ export default function BenefitPage() { width={600} footer={null} open={isDeleteOpne} - onCancel={closeDeleteModal} - onClick={openDeleteModal} + onCancel={() => handleModal(setIsDeleteOpen, 'delete', 'close')} + onClick={() => handleModal(setIsDeleteOpen, 'delete', 'open')} isDelete > handleModal(setIsDeleteOpen, 'delete', 'close')} /> handleModal(setIsModifyOpen, 'modify', 'close')} + onClick={() => handleModal(setIsModifyOpen, 'modify', 'open')} destroyOnClose isDelete key={selected} > handleModal(setIsModifyOpen, 'modify', 'close')} selected={selected} /> @@ -120,10 +111,10 @@ export default function BenefitPage() { width={900} footer open={isCreateOpen} - onCancel={closeCreateModal} - onClick={openCreateModal} + onCancel={() => handleModal(setIsCreateOpen, 'create', 'close')} + onClick={() => handleModal(setIsCreateOpen, 'create', 'open')} > - + handleModal(setIsCreateOpen, 'create', 'close')} /> @@ -139,34 +130,63 @@ export default function BenefitPage() { + handleModal(setIsBenefitDetailOpen, 'benefitDetail', 'close')} + onClick={() => { + if (selectedShop.length === 0) return; + handleModal(setIsBenefitDetailOpen, 'benefitDetail', 'open'); + }} + > + selectedShop.includes(shop.id))} + closeBenefitModifyModal={() => handleModal(setIsBenefitDetailOpen, 'benefitDetail', 'close')} + /> + handleModal(setIsAdditionOpen, 'addition', 'close')} + onClick={() => handleModal(setIsAdditionOpen, 'addition', 'open')} > - + handleModal(setIsAdditionOpen, 'addition', 'close')} /> - - {selected ? ( - + {selected ? ( + + + + 상점명 + 상세정보 + + + {data?.shops.map((shop) => ( - onShopClick(shop.id)} key={shop.id} > - {shop.name} - + + {shop.name} + + + {shop.detail} + + ))} - - ) : null} - + + + + ) : null} ); diff --git a/src/store/api/benefit/index.ts b/src/store/api/benefit/index.ts index e860a818..d1d97541 100644 --- a/src/store/api/benefit/index.ts +++ b/src/store/api/benefit/index.ts @@ -2,7 +2,7 @@ import { createApi } from '@reduxjs/toolkit/query/react'; import { BenefitCategoryResponse, GetBenefitShopsResponse, SearchResponse, CreateBenefitResponse, CreateBenefitRequest, DeleteShopsRequest, - AddShopRequest, ModifyBenefitRequest, + AddShopRequest, ModifyBenefitRequest, ModifyBenefitDetails, } from 'model/benefit.model'; import baseQueryReauth from 'store/api/baseQueryReauth'; @@ -55,11 +55,11 @@ export const benefitApi = createApi({ invalidatesTags: () => [{ type: 'benefit' }], }), addBenefitShops: builder.mutation({ - query({ id, shop_ids }) { + query({ id, shop_details }) { return { url: `admin/benefit/${id}/shops`, method: 'post', - body: { shop_ids }, + body: { shop_details }, }; }, invalidatesTags: () => [{ type: 'benefit' }], @@ -74,11 +74,21 @@ export const benefitApi = createApi({ }, invalidatesTags: () => [{ type: 'benefit' }], }), + modifyBenefitDetails: builder.mutation({ + query({ modify_details }) { + return { + url: 'admin/benefit', + method: 'put', + body: { modify_details }, + }; + }, + invalidatesTags: () => [{ type: 'benefit' }], + }), }), }); export const { useGetBenefitCategoryQuery, useGetBenefitShopsQuery, useSearchShopsQuery, useCreateBenefitCategoryMutation, useDeleteBenefitCategoryMutation, useDeleteBenefitShopsMutation, - useAddBenefitShopsMutation, useModifyBenefitCategoryMutation, + useAddBenefitShopsMutation, useModifyBenefitCategoryMutation, useModifyBenefitDetailsMutation, } = benefitApi;