From 6247da1c990f672677d545fbbc9b07551a5ed0d6 Mon Sep 17 00:00:00 2001 From: kimdaye77 <63107805+kimdaye77@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:25:43 +0900 Subject: [PATCH 1/7] =?UTF-8?q?[STYLE]=20#295:=20=EA=B8=80=EC=9E=90=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/cards/ModelTypeCard.tsx | 4 ++-- frontend/src/components/cards/OptionCard.tsx | 4 ++-- frontend/src/components/common/banner/Banner.tsx | 1 - frontend/src/components/tabs/OptionTab.tsx | 16 ++++++++++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/cards/ModelTypeCard.tsx b/frontend/src/components/cards/ModelTypeCard.tsx index 8063d63..ff79995 100644 --- a/frontend/src/components/cards/ModelTypeCard.tsx +++ b/frontend/src/components/cards/ModelTypeCard.tsx @@ -1,6 +1,6 @@ import { styled } from 'styled-components'; import { CheckIcon } from '../common/icons/Icons'; -import { BodyKrRegular4, HeadingEn4, HeadingKrMedium7 } from '../../styles/typefaces'; +import { BodyKrRegular4, HeadingKrMedium6, HeadingKrMedium7 } from '../../styles/typefaces'; import DefaultCardStyle from '../common/card/DefaultCardStyle'; import { HTMLAttributes } from 'react'; @@ -35,7 +35,7 @@ const Wrapper = styled(DefaultCardStyle)` `; const ModelTypeTitle = styled.div` - ${HeadingEn4} + ${HeadingKrMedium6} `; const ModelTypePrice = styled.div` ${HeadingKrMedium7} diff --git a/frontend/src/components/cards/OptionCard.tsx b/frontend/src/components/cards/OptionCard.tsx index f112924..f8588b9 100644 --- a/frontend/src/components/cards/OptionCard.tsx +++ b/frontend/src/components/cards/OptionCard.tsx @@ -1,5 +1,5 @@ import { styled } from 'styled-components'; -import { BodyKrRegular4, HeadingEn4, HeadingKrMedium7 } from '../../styles/typefaces'; +import { BodyKrRegular4, HeadingKrMedium6, HeadingKrMedium7 } from '../../styles/typefaces'; import { CheckIcon } from '../common/icons/Icons'; import DefaultCardStyle from '../common/card/DefaultCardStyle'; import { HTMLAttributes } from 'react'; @@ -97,7 +97,7 @@ const OptionCardInfo = styled.div` `; const OptionTitle = styled.div` - ${HeadingEn4} + ${HeadingKrMedium6} overflow: hidden; text-overflow: ellipsis; display: -webkit-box; diff --git a/frontend/src/components/common/banner/Banner.tsx b/frontend/src/components/common/banner/Banner.tsx index 4968c73..59a8bfe 100644 --- a/frontend/src/components/common/banner/Banner.tsx +++ b/frontend/src/components/common/banner/Banner.tsx @@ -47,7 +47,6 @@ const SubTitle = styled.p` const Title = styled.p` position: relative; - z-index: 3; color: ${({ theme }) => theme.color.primaryColor700}; ${HeadingKrBold1} `; diff --git a/frontend/src/components/tabs/OptionTab.tsx b/frontend/src/components/tabs/OptionTab.tsx index 25d5c32..9e64a0a 100644 --- a/frontend/src/components/tabs/OptionTab.tsx +++ b/frontend/src/components/tabs/OptionTab.tsx @@ -1,4 +1,12 @@ -import { Dispatch, HTMLAttributes, SetStateAction, useEffect, useRef, useState } from 'react'; +import { + Dispatch, + Fragment, + HTMLAttributes, + SetStateAction, + useEffect, + useRef, + useState, +} from 'react'; import { BodyKrMedium3, BodyKrRegular3, BodyKrRegular4 } from '../../styles/typefaces'; import styled, { css, useTheme } from 'styled-components'; import { ArrowLeft, ArrowRight } from '../common/icons/Icons'; @@ -92,8 +100,8 @@ export default function OptionTab({ options, setBannerInfo }: ISubOptionTab) { {chunkedOptions.map((optionGroup: ISubOptionList[], groupIndex) => ( - <> - + + {optionGroup.map((option: ISubOptionList, index: number) => ( ))} - + ))} From 6733a71266f76550349c31bd93a5b2526132278a Mon Sep 17 00:00:00 2001 From: jijiseong Date: Sat, 19 Aug 2023 14:04:51 +0900 Subject: [PATCH 2/7] =?UTF-8?q?[FEAT]=20#315:=20=EA=B2=AC=EC=A0=81?= =?UTF-8?q?=EC=9A=94=EC=95=BD=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 2 + .../common/buttons/ToggleButtons.tsx | 45 ++++ frontend/src/components/details/Details.tsx | 9 +- .../src/components/details/SummaryItem.tsx | 3 +- .../components/modal/QuoteSummaryModal.tsx | 209 ++++++++++++++ frontend/src/components/modal/WhiteModal.tsx | 18 ++ .../src/components/summary/PriceSummary.tsx | 14 +- .../src/containers/Modal/ModalContainer.tsx | 4 + .../OuterColorBannerContainer.tsx | 27 +- .../OuterColorSelectContainer.tsx | 9 +- .../containers/ResultPage/DetailContainer.tsx | 254 +++++++----------- .../ResultPage/ResultFooterContainer.tsx | 6 +- frontend/src/context/ItemProvider.tsx | 8 +- frontend/src/context/OuterColorProvider.tsx | 25 +- .../src/context/QuoteSummaryModalProvider.tsx | 27 ++ frontend/src/pages/OuterColorPage.tsx | 19 +- 16 files changed, 470 insertions(+), 209 deletions(-) create mode 100644 frontend/src/components/common/buttons/ToggleButtons.tsx create mode 100644 frontend/src/components/modal/QuoteSummaryModal.tsx create mode 100644 frontend/src/components/modal/WhiteModal.tsx create mode 100644 frontend/src/context/QuoteSummaryModalProvider.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1a7d7b2..1736254 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,7 @@ import SimilarQuoteModalProvider from './context/SimilarQuoteModalProvider'; import GuideModalProvider from './context/GuideModalProvider'; import ItemProvider from './context/ItemProvider'; import { useEffect } from 'react'; +import QuoteSummaryModalProvider from './context/QuoteSummaryModalProvider'; function App() { useEffect(() => { @@ -19,6 +20,7 @@ function App() { CloseModalProvider, SimilarQuoteModalProvider, GuideModalProvider, + QuoteSummaryModalProvider, ItemProvider, ]; return ( diff --git a/frontend/src/components/common/buttons/ToggleButtons.tsx b/frontend/src/components/common/buttons/ToggleButtons.tsx new file mode 100644 index 0000000..e17fe27 --- /dev/null +++ b/frontend/src/components/common/buttons/ToggleButtons.tsx @@ -0,0 +1,45 @@ +import { styled } from 'styled-components'; +import { BodyKrRegular3 } from '../../../styles/typefaces'; +import { flexCenterCss } from '../../../utils/commonStyle'; +import { useState } from 'react'; + +type buttonType = 'outerColor' | 'innerColor'; + +export default function ToggleButtons() { + const [selectedButton, setSelectedButton] = useState('outerColor'); + + const toggle = () => { + if (selectedButton === 'outerColor') { + setSelectedButton('innerColor'); + } else { + setSelectedButton('outerColor'); + } + }; + + return ( + + + + + ); +} + +const Button = styled.button<{ $active: boolean }>` + width: 100%; + ${BodyKrRegular3} + height: 36px; + border-radius: 18px; + color: ${({ $active, theme }) => ($active ? theme.color.white : theme.color.primaryColor)}; + background-color: ${({ $active, theme }) => ($active ? theme.color.primaryColor : 'none')}; +`; +const ToggleButtonContainer = styled.div` + ${flexCenterCss} + justify-content: space-between; + padding: 0 5px; + gap: 5px; + width: 213px; + height: 48px; + border-radius: 24px; + border: 1px solid ${({ theme }) => theme.color.gray100}; + cursor: pointer; +`; diff --git a/frontend/src/components/details/Details.tsx b/frontend/src/components/details/Details.tsx index f933f61..56896e3 100644 --- a/frontend/src/components/details/Details.tsx +++ b/frontend/src/components/details/Details.tsx @@ -1,24 +1,24 @@ import { styled, useTheme } from 'styled-components'; import { HTMLAttributes } from 'react'; -import { HeadingKrMedium5 } from '../../styles/typefaces'; +import { BodyKrMedium3, HeadingKrMedium5 } from '../../styles/typefaces'; import { ArrowDown, ArrowUp } from '../common/icons/Icons'; interface IDetails extends HTMLAttributes { title: string; open?: boolean; + desc: string; } -export default function Details({ title, open = false, ...props }: IDetails) { +export default function Details({ title, open = false, desc, ...props }: IDetails) { const theme = useTheme(); const arrowColor = theme.color.gray900; - const totalPrice = 1_000_000; return ( {title} - +{totalPrice.toLocaleString()}원 + {desc} {open ? : } @@ -46,6 +46,7 @@ const Price = styled.span` margin-right: 20px; `; const RightDiv = styled.div` + ${BodyKrMedium3} display: flex; align-items: center; color: ${({ theme }) => theme.color.primaryColor}; diff --git a/frontend/src/components/details/SummaryItem.tsx b/frontend/src/components/details/SummaryItem.tsx index de0b99f..2dbd396 100644 --- a/frontend/src/components/details/SummaryItem.tsx +++ b/frontend/src/components/details/SummaryItem.tsx @@ -19,7 +19,7 @@ export default function SummaryItem({ imgSrc, itemName, selectedName, price }: I 수정하기 - + {price}원 + + {price.toLocaleString()}원 @@ -62,5 +62,6 @@ const ItemName = styled.div` `; const ModifyButton = styled.button` color: ${({ theme }) => theme.color.primaryColor}; + text-align: end; `; const SelectedName = styled.div``; diff --git a/frontend/src/components/modal/QuoteSummaryModal.tsx b/frontend/src/components/modal/QuoteSummaryModal.tsx new file mode 100644 index 0000000..d201d7a --- /dev/null +++ b/frontend/src/components/modal/QuoteSummaryModal.tsx @@ -0,0 +1,209 @@ +import { HTMLAttributes, useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { styled } from 'styled-components'; +import { QuoteSummaryModalContext } from '../../context/QuoteSummaryModalProvider'; +import { PATH } from '../../utils/constants'; +import { ItemContext } from '../../context/ItemProvider'; +import RectButton from '../common/buttons/RectButton'; +import { CloseIcon } from '../common/icons/Icons'; +import ToggleButtons from '../common/buttons/ToggleButtons'; +import { flexCenterCss } from '../../utils/commonStyle'; +import { + BodyKrRegular3, + BodyKrRegular4, + HeadingKrMedium2, + HeadingKrMedium5, +} from '../../styles/typefaces'; +import { DimmedBackground } from './DimmedBackground'; +import WhiteModal from './WhiteModal'; + +interface IQuoteSummaryModal extends HTMLAttributes {} + +export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { + const { selectedItem, totalPrice } = useContext(ItemContext); + const { visible, setVisible } = useContext(QuoteSummaryModalContext); + const navigate = useNavigate(); + + const handleOkButton = () => { + setVisible(false); + navigate(PATH.result); + }; + + const optionNames = ['차량', '옵션', '들의', '임시', '목록', '목록입니다', '목록입니다']; + // const optionNames = selectedItem.options.map((option, idx) => { + // return option.name; + // }); + + return ( + + e.stopPropagation()}> +
+ 견적요약 + + + +
+ + + + + + + + + +
+ + + +
+ + +
+ +
+
+ + 현재 총 가격 + {totalPrice.toLocaleString()} 원 + + +
+ + 견적 완료하기 + +
+
+
+ ); +} + +function Detail({ title, name, price }: { title: string; name: string; price: number }) { + return ( + + {title} + {name} + + {price.toLocaleString()}원 + + ); +} + +const QuoteModal = styled(WhiteModal)` + display: flex; + justify-content: space-between; + flex-direction: column; + width: 850px; + height: 520px; +`; + +const OkButton = styled(RectButton)``; + +const Header = styled.div` + ${flexCenterCss} + height: 53px; +`; +const ModalTitle = styled.h1` + ${HeadingKrMedium5} +`; +const CloseButton = styled.button` + position: absolute; + right: 10px; +`; +const Row = styled.div` + ${flexCenterCss} + gap: 32px; + margin-bottom: 40px; +`; +const Body = styled.div` + padding: 24px 36px; +`; +const ImgSection = styled.div` + ${flexCenterCss} + width: 50%; + flex-direction: column; +`; +const CarImg = styled.img` + height: auto; +`; +const DetailSection = styled.div` + display: flex; + flex-direction: column; + width: 50%; + gap: 8px; +`; + +const DetailWrapper = styled.div` + display: flex; + justify-content: space-between; + ${BodyKrRegular3} +`; +const DetailTitle = styled.div` + color: ${({ theme }) => theme.color.gray500}; + text-align: start; + width: 100px; +`; +const DetailName = styled.div` + display: inline-block; + color: ${({ theme }) => theme.color.gray900}; + text-align: start; + width: 210px; + overflow-x: scroll; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + &::-webkit-scrollbar { + display: none; + } +`; +const DetailPrice = styled.div` + color: ${({ theme }) => theme.color.gray900}; + text-align: end; + margin-left: auto; +`; +const PriceSection = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; +`; +const PriceCaption = styled.span` + ${BodyKrRegular4} + color: ${({ theme }) => theme.color.gray700}; +`; +const Price = styled.span` + ${HeadingKrMedium2} + color: ${({ theme }) => theme.color.primaryColor}; + margin-left: 16px; +`; + +const Footer = styled.div``; + +const Hr = styled.div` + width: 100%; + height: 1px; + background-color: ${({ theme }) => theme.color.gray100}; + margin: 7px 0; +`; diff --git a/frontend/src/components/modal/WhiteModal.tsx b/frontend/src/components/modal/WhiteModal.tsx new file mode 100644 index 0000000..5c52819 --- /dev/null +++ b/frontend/src/components/modal/WhiteModal.tsx @@ -0,0 +1,18 @@ +import { HTMLAttributes } from 'react'; +import { styled } from 'styled-components'; + +interface IWhiteModal extends HTMLAttributes {} + +export default function WhiteModal({ ...props }: IWhiteModal) { + return ; +} + +const Wrapper = styled.div` + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + overflow: hidden; + border-radius: 4px; + background-color: ${({ theme }) => theme.color.white}; +`; diff --git a/frontend/src/components/summary/PriceSummary.tsx b/frontend/src/components/summary/PriceSummary.tsx index 3762a72..30882cb 100644 --- a/frontend/src/components/summary/PriceSummary.tsx +++ b/frontend/src/components/summary/PriceSummary.tsx @@ -5,6 +5,7 @@ import { BodyKrRegular4, HeadingKrMedium2 } from '../../styles/typefaces'; import { useNavigate } from 'react-router-dom'; import { useContext } from 'react'; import { ItemContext } from '../../context/ItemProvider'; +import { QuoteSummaryModalContext } from '../../context/QuoteSummaryModalProvider'; interface IPriceSummary extends React.HTMLAttributes { nextPagePath: string; @@ -12,20 +13,25 @@ interface IPriceSummary extends React.HTMLAttributes { export default function PriceSummary({ nextPagePath, ...props }: IPriceSummary) { const { totalPrice } = useContext(ItemContext); - + const { setVisible: setQuoteSummaryModalVisible } = useContext(QuoteSummaryModalContext); const navigate = useNavigate(); - const handleButtonClick = () => { + const handleNextButtonClick = () => { navigate(nextPagePath); }; + const handleSummaryButtonClick = () => { + setQuoteSummaryModalVisible(true); + }; return ( - 견적 요약 + + 견적 요약 + 현재 총 가격{totalPrice.toLocaleString()} 원 - + 다음 diff --git a/frontend/src/containers/Modal/ModalContainer.tsx b/frontend/src/containers/Modal/ModalContainer.tsx index 5261311..690e45f 100644 --- a/frontend/src/containers/Modal/ModalContainer.tsx +++ b/frontend/src/containers/Modal/ModalContainer.tsx @@ -5,17 +5,21 @@ import SimilarQuoteModal from '../../components/modal/SimilarQuoteModal'; import { GuideModalContext } from '../../context/GuideModalProvider'; import { SimilarQuoteModalContext } from '../../context/SimilarQuoteModalProvider'; import { CloseModalContext } from '../../context/CloseModalProvider'; +import QuoteSummaryModal from '../../components/modal/QuoteSummaryModal'; +import { QuoteSummaryModalContext } from '../../context/QuoteSummaryModalProvider'; export default function ModalContainer() { const { setVisible: setCloseModalVisible } = useContext(CloseModalContext); const { setVisible: setGuideModalVisible } = useContext(GuideModalContext); const { setVisible: setSimilarQuoteModalVisible } = useContext(SimilarQuoteModalContext); + const { setVisible: setQuoteSummaryModalVisible } = useContext(QuoteSummaryModalContext); return ( <> setCloseModalVisible(false)} /> setGuideModalVisible(false)} /> setSimilarQuoteModalVisible(false)} /> + setQuoteSummaryModalVisible(false)} /> ); } diff --git a/frontend/src/containers/OuterColorPage/OuterColorBannerContainer.tsx b/frontend/src/containers/OuterColorPage/OuterColorBannerContainer.tsx index 5ab83b9..1858dfc 100644 --- a/frontend/src/containers/OuterColorPage/OuterColorBannerContainer.tsx +++ b/frontend/src/containers/OuterColorPage/OuterColorBannerContainer.tsx @@ -3,17 +3,13 @@ import { styled } from 'styled-components'; import Banner from '../../components/common/banner/Banner'; import CenterWrapper from '../../components/layout/CenterWrapper'; import { flexCenterCss } from '../../utils/commonStyle'; -import { useFetch } from '../../hooks/useFetch'; -import { IMG_URL, OUTER_IMG_API } from '../../utils/apis'; +import { IMG_URL } from '../../utils/apis'; import Loading from '../../components/loading/Loading'; import { OuterColorContext } from '../../context/OuterColorProvider'; import car360Reducer from '../../reducer/car360Reducer'; export default function OuterColorBannerContainer() { - const { seletedColorId } = useContext(OuterColorContext); - const { data: car360ImgUrls, loading } = useFetch( - `${OUTER_IMG_API}?colorid=${seletedColorId}` - ); + const { car360UrlsData } = useContext(OuterColorContext); const [imgState, setImgState] = useReducer(car360Reducer, { visibleIdx: 0, isDragging: false, @@ -21,14 +17,13 @@ export default function OuterColorBannerContainer() { startIdx: 0, imgLoading: true, }); - const imgLen = car360ImgUrls ? car360ImgUrls.length : 0; + const imgLen = car360UrlsData ? car360UrlsData.length : 0; const handleMousedown: MouseEventHandler = useCallback( ({ pageX }) => { setImgState({ type: 'SET_IS_DRAGGING', value: true }); setImgState({ type: 'SET_START_X', value: pageX }); setImgState({ type: 'SET_START_IDX', value: imgState.visibleIdx }); - console.log(imgState.visibleIdx); }, [imgState] ); @@ -67,22 +62,22 @@ export default function OuterColorBannerContainer() { }, []); const downloadAndSaveImages = useCallback( - async (car360ImgUrls: string[], abortController: AbortController) => { - if (isLoaded(car360ImgUrls)) { + async (car360UrlsData: string[], abortController: AbortController) => { + if (isLoaded(car360UrlsData)) { setImgState({ type: 'SET_IMG_LOADING', value: false }); return; } setImgState({ type: 'SET_IMG_LOADING', value: true }); await Promise.all( - car360ImgUrls.map(async (url, idx) => { + car360UrlsData.map(async (url, idx) => { const isImageExist = localStorage.getItem(url) !== null; if (isImageExist) return; const res = await fetch(IMG_URL + url, { signal: abortController.signal, }); const imgBlob = await res.blob(); - const localStorageKey = car360ImgUrls[idx]; + const localStorageKey = car360UrlsData[idx]; localStorage.setItem(localStorageKey, URL.createObjectURL(imgBlob)); return imgBlob; }) @@ -93,15 +88,15 @@ export default function OuterColorBannerContainer() { ); useEffect(() => { - if (!car360ImgUrls || loading) return; + if (!car360UrlsData) return; const abortController = new AbortController(); - downloadAndSaveImages(car360ImgUrls, abortController); + downloadAndSaveImages(car360UrlsData, abortController); return () => { abortController.abort(); }; - }, [downloadAndSaveImages, loading, car360ImgUrls]); + }, [downloadAndSaveImages, car360UrlsData]); - const car360Components = car360ImgUrls?.map((url, idx) => { + const car360Components = car360UrlsData?.map((url, idx) => { const imgSrc = localStorage.getItem(url); if (!imgSrc) return; return ; diff --git a/frontend/src/containers/OuterColorPage/OuterColorSelectContainer.tsx b/frontend/src/containers/OuterColorPage/OuterColorSelectContainer.tsx index f6e16c0..34bbca0 100644 --- a/frontend/src/containers/OuterColorPage/OuterColorSelectContainer.tsx +++ b/frontend/src/containers/OuterColorPage/OuterColorSelectContainer.tsx @@ -9,12 +9,8 @@ import { IOuterColor, ISelected, OuterColorContext } from '../../context/OuterCo import { ItemContext } from '../../context/ItemProvider'; export default function OuterColorSelectContainer() { - const { - data: outerColorData, - selectedIdx, - setSelectedIdx, - setSelectedColorId, - } = useContext(OuterColorContext); + const { outerColorData, selectedIdx, setSelectedIdx, setSelectedColorId } = + useContext(OuterColorContext); const [cardPageList, setCardPageList] = useState(); const { totalPrice, setSelectedItem, setTotalPrice } = useContext(ItemContext); const prevTotalPrice = useRef(totalPrice); @@ -31,6 +27,7 @@ export default function OuterColorSelectContainer() { id: selectedItem.colorId, name: selectedItem.colorName, imgSrc: selectedItem.colorImage, + carImgSrc: '', price: selectedItem.colorPrice, title: '외장 색상', }, diff --git a/frontend/src/containers/ResultPage/DetailContainer.tsx b/frontend/src/containers/ResultPage/DetailContainer.tsx index 26de4f0..74f1e19 100644 --- a/frontend/src/containers/ResultPage/DetailContainer.tsx +++ b/frontend/src/containers/ResultPage/DetailContainer.tsx @@ -2,9 +2,16 @@ import { styled } from 'styled-components'; import { HeadingKrMedium7 } from '../../styles/typefaces'; import Details from '../../components/details/Details'; import SummaryItem from '../../components/details/SummaryItem'; -import { useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; +import { ItemContext } from '../../context/ItemProvider'; export default function DetailContainer() { + const { selectedItem } = useContext(ItemContext); + const [detailPrice, setDetailPrice] = useState({ + modelTypePrice: 0, + colorPrice: 0, + optionPrice: 0, + }); const [isOpen, setIsOepn] = useState<{ [key: number]: boolean }>({ 1: true, 2: true, @@ -24,185 +31,112 @@ export default function DetailContainer() { }); }; + useEffect(() => { + const modelTypePrice = + selectedItem.modelType.powerTrain.price + + selectedItem.modelType.bodyType.price + + selectedItem.modelType.operation.price; + const colorPrice = selectedItem.innerColor.price + selectedItem.outerColor.price; + const optionPrice = selectedItem.options.reduce((acc, option) => acc + option.price, 0); + + setDetailPrice({ + modelTypePrice, + colorPrice, + optionPrice, + }); + }, [selectedItem]); + + const optionSummaryItems = selectedItem.options.map((option, idx) => { + return ( + + ); + }); + return ( 상세 견적 -
setOpenedIdx(1)}> +
setOpenedIdx(1)} + >
-
setOpenedIdx(2)}> +
setOpenedIdx(2)} + > - - - -
-
setOpenedIdx(3)}> - - - - -
-
setOpenedIdx(4)}> - - - -
-
setOpenedIdx(5)}> - - - - - -
-
setOpenedIdx(6)}> - - - - - -
-
setOpenedIdx(7)}> - - - - - -
-
setOpenedIdx(8)}> - - - - - +
setOpenedIdx(3)} + > + {optionSummaryItems}
+
setOpenedIdx(4)}>
+
setOpenedIdx(5)} + >
+
setOpenedIdx(6)} + >
+
setOpenedIdx(7)} + >
+
setOpenedIdx(8)}>
); } diff --git a/frontend/src/containers/ResultPage/ResultFooterContainer.tsx b/frontend/src/containers/ResultPage/ResultFooterContainer.tsx index f6d98a1..82a841a 100644 --- a/frontend/src/containers/ResultPage/ResultFooterContainer.tsx +++ b/frontend/src/containers/ResultPage/ResultFooterContainer.tsx @@ -2,13 +2,17 @@ import { styled } from 'styled-components'; import CenterWrapper from '../../components/layout/CenterWrapper'; import { BodyKrRegular3, HeadingKrMedium2 } from '../../styles/typefaces'; import RectButton from '../../components/common/buttons/RectButton'; +import { useContext } from 'react'; +import { ItemContext } from '../../context/ItemProvider'; export default function ResultFooterContainer() { + const { totalPrice } = useContext(ItemContext); + return ( 최종 견적 가격 - 100원 + {totalPrice.toLocaleString()} 원 공유하기 diff --git a/frontend/src/context/ItemProvider.tsx b/frontend/src/context/ItemProvider.tsx index 95eb0e8..fee622f 100644 --- a/frontend/src/context/ItemProvider.tsx +++ b/frontend/src/context/ItemProvider.tsx @@ -9,6 +9,9 @@ interface detailItemType extends defaultItemType { title: string; imgSrc: string; } +interface IOuterColorItemType extends detailItemType { + carImgSrc: string; +} interface ISelectedItem { trim: defaultItemType; modelType: { @@ -17,7 +20,7 @@ interface ISelectedItem { operation: detailItemType; }; innerColor: detailItemType; - outerColor: detailItemType; + outerColor: IOuterColorItemType; options: detailItemType[]; } @@ -73,6 +76,7 @@ const initialSelectedItem = { name: '', title: '', imgSrc: '', + carImgSrc: '', price: 0, }, options: [{ id: 0, name: '', title: '', imgSrc: '', price: 0 }], @@ -91,7 +95,7 @@ type actionType = | { type: 'SET_OPERATION'; value: detailItemType } | { type: 'SET_BODY_TYPE'; value: detailItemType } | { type: 'SET_INNER_COLOR'; value: detailItemType } - | { type: 'SET_OUTER_COLOR'; value: detailItemType } + | { type: 'SET_OUTER_COLOR'; value: IOuterColorItemType } | { type: 'SET_OPTIONS'; value: detailItemType[] }; const reducer = (state: ISelectedItem, action: actionType): ISelectedItem => { diff --git a/frontend/src/context/OuterColorProvider.tsx b/frontend/src/context/OuterColorProvider.tsx index 780834f..1a8b21e 100644 --- a/frontend/src/context/OuterColorProvider.tsx +++ b/frontend/src/context/OuterColorProvider.tsx @@ -16,42 +16,53 @@ export interface ISelected { } interface IOuterColorContext { - data: IOuterColor[] | null; + outerColorData: IOuterColor[] | null; + car360UrlsData: string[] | null; loading: boolean; selectedIdx: ISelected; seletedColorId: number; - setData: Dispatch>; + setOuterColorData: Dispatch>; setLoading: Dispatch>; setSelectedIdx: Dispatch>; setSelectedColorId: Dispatch>; + setCar360UrlsData: Dispatch>; } const initialContext: IOuterColorContext = { - data: null, + outerColorData: null, + car360UrlsData: [], loading: true, selectedIdx: { page: 0, idx: 0 }, seletedColorId: 3, setLoading: () => {}, - setData: () => {}, + setOuterColorData: () => {}, setSelectedIdx: () => {}, setSelectedColorId: () => {}, + setCar360UrlsData: () => {}, }; export const OuterColorContext = createContext(initialContext); export default function OuterColorProvider({ children }: IOuterColorProvider) { - const [data, setData] = useState(initialContext.data); + const [outerColorData, setOuterColorData] = useState( + initialContext.outerColorData + ); + const [car360UrlsData, setCar360UrlsData] = useState( + initialContext.car360UrlsData + ); const [selectedIdx, setSelectedIdx] = useState(initialContext.selectedIdx); const [seletedColorId, setSelectedColorId] = useState(initialContext.seletedColorId); const [loading, setLoading] = useState(initialContext.loading); const providerValue = { - data, + outerColorData, + car360UrlsData, selectedIdx, loading, seletedColorId, setSelectedIdx, - setData, + setOuterColorData, + setCar360UrlsData, setLoading, setSelectedColorId, }; diff --git a/frontend/src/context/QuoteSummaryModalProvider.tsx b/frontend/src/context/QuoteSummaryModalProvider.tsx new file mode 100644 index 0000000..36fb268 --- /dev/null +++ b/frontend/src/context/QuoteSummaryModalProvider.tsx @@ -0,0 +1,27 @@ +import { ReactNode, createContext, useState } from 'react'; + +interface IQuoteSummaryModalContext { + visible: boolean; + setVisible: React.Dispatch>; +} + +interface IQuoteSummaryModalProvider { + children: ReactNode; +} + +const initialContext = { + visible: false, + setVisible: () => {}, +}; + +export const QuoteSummaryModalContext = createContext(initialContext); + +export default function QuoteSummaryModalProvider({ children }: IQuoteSummaryModalProvider) { + const [visible, setVisible] = useState(initialContext.visible); + + return ( + + {children} + + ); +} diff --git a/frontend/src/pages/OuterColorPage.tsx b/frontend/src/pages/OuterColorPage.tsx index eb5b13c..55f048a 100644 --- a/frontend/src/pages/OuterColorPage.tsx +++ b/frontend/src/pages/OuterColorPage.tsx @@ -1,18 +1,21 @@ +import { useContext, useEffect } from 'react'; import OuterColorSelectContainer from '../containers/OuterColorPage/OuterColorSelectContainer'; import OuterColorBannerContainer from '../containers/OuterColorPage/OuterColorBannerContainer'; -import { useFetch } from '../hooks/useFetch'; -import { OUTER_COLOR_API } from '../utils/apis'; -import { useContext, useEffect } from 'react'; import { IOuterColor, OuterColorContext } from '../context/OuterColorProvider'; +import { useFetch } from '../hooks/useFetch'; +import { OUTER_COLOR_API, OUTER_IMG_API } from '../utils/apis'; export default function OuterColorPage() { - const { data, loading } = useFetch(`${OUTER_COLOR_API}?carid=${1}`); // Todo. selectedItem.trim.id로 바꾸기 - const { setData, setLoading } = useContext(OuterColorContext); + const { seletedColorId, setOuterColorData, setCar360UrlsData } = useContext(OuterColorContext); + const { data: outerColorData } = useFetch(`${OUTER_COLOR_API}?carid=${1}`); // Todo. selectedItem.trim.id로 바꾸기 + const { data: car360ImgUrls } = useFetch(`${OUTER_IMG_API}?colorid=${seletedColorId}`); useEffect(() => { - setData(data); - setLoading(loading); - }, [data, loading, setData, setLoading]); + setOuterColorData(outerColorData); + }, [outerColorData, setOuterColorData]); + useEffect(() => { + setCar360UrlsData(car360ImgUrls); + }, [car360ImgUrls, setCar360UrlsData]); return ( <> From 170ddc383decb2d02e745533e405a1b4a5e93c89 Mon Sep 17 00:00:00 2001 From: jijiseong Date: Sat, 19 Aug 2023 14:24:19 +0900 Subject: [PATCH 3/7] =?UTF-8?q?[REFACTOR]=20#316:=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=EA=B0=92=20=EB=B9=88=EA=B0=92=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/modal/QuoteSummaryModal.tsx | 25 ++++++++++++------- frontend/src/context/ItemProvider.tsx | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/modal/QuoteSummaryModal.tsx b/frontend/src/components/modal/QuoteSummaryModal.tsx index d201d7a..2e1affa 100644 --- a/frontend/src/components/modal/QuoteSummaryModal.tsx +++ b/frontend/src/components/modal/QuoteSummaryModal.tsx @@ -18,16 +18,23 @@ import { DimmedBackground } from './DimmedBackground'; import WhiteModal from './WhiteModal'; interface IQuoteSummaryModal extends HTMLAttributes {} - +interface IDetail { + title: string; + name: string; + price: number; +} export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { const { selectedItem, totalPrice } = useContext(ItemContext); const { visible, setVisible } = useContext(QuoteSummaryModalContext); const navigate = useNavigate(); - const handleOkButton = () => { + const handleOkButtonClick = () => { setVisible(false); navigate(PATH.result); }; + const handleCloseButtonClicke = () => { + setVisible(false); + }; const optionNames = ['차량', '옵션', '들의', '임시', '목록', '목록입니다', '목록입니다']; // const optionNames = selectedItem.options.map((option, idx) => { @@ -39,7 +46,7 @@ export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { e.stopPropagation()}>
견적요약 - +
@@ -93,7 +100,7 @@ export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) {
- + 견적 완료하기
@@ -102,12 +109,12 @@ export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { ); } -function Detail({ title, name, price }: { title: string; name: string; price: number }) { +function Detail({ title, name, price }: IDetail) { return ( {title} {name} - + {price.toLocaleString()}원 + + {price.toLocaleString()} 원 ); } @@ -170,14 +177,14 @@ const DetailName = styled.div` display: inline-block; color: ${({ theme }) => theme.color.gray900}; text-align: start; - width: 210px; - overflow-x: scroll; - overflow-x: auto; + width: 140px; + overflow-x: hidden; overflow-y: hidden; white-space: nowrap; &::-webkit-scrollbar { display: none; } + text-overflow: ellipsis; `; const DetailPrice = styled.div` color: ${({ theme }) => theme.color.gray900}; diff --git a/frontend/src/context/ItemProvider.tsx b/frontend/src/context/ItemProvider.tsx index fee622f..61e20ad 100644 --- a/frontend/src/context/ItemProvider.tsx +++ b/frontend/src/context/ItemProvider.tsx @@ -79,7 +79,7 @@ const initialSelectedItem = { carImgSrc: '', price: 0, }, - options: [{ id: 0, name: '', title: '', imgSrc: '', price: 0 }], + options: [], }; const initialItem: IItemContext = { From ffb7a7dff1329e133baaa34afe75bf47dd1a5467 Mon Sep 17 00:00:00 2001 From: kimdaye77 <63107805+kimdaye77@users.noreply.github.com> Date: Sat, 19 Aug 2023 14:27:35 +0900 Subject: [PATCH 4/7] =?UTF-8?q?[FEAT]=20#295:=20=EA=B0=80=EA=B2=A9=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubOptionContainer.tsx | 37 ++++++++++++++++--- frontend/src/context/ItemProvider.tsx | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/frontend/src/containers/OptionPage/OptionSelectContainer/SubOptionContainer.tsx b/frontend/src/containers/OptionPage/OptionSelectContainer/SubOptionContainer.tsx index 2e56d85..d91aed3 100644 --- a/frontend/src/containers/OptionPage/OptionSelectContainer/SubOptionContainer.tsx +++ b/frontend/src/containers/OptionPage/OptionSelectContainer/SubOptionContainer.tsx @@ -4,11 +4,13 @@ import RoundButton from '../../../components/common/buttons/RoundButton'; import OptionCard from '../../../components/cards/OptionCard'; import { ISubOption, SubOptionContext } from '../../../context/SubOptionProvider'; import HmgTag from '../../../components/common/hmgTag/HmgTag'; +import { ItemContext } from '../../../context/ItemProvider'; export default function SubOptionContainer() { const [currentCategory, setCurrentCategory] = useState('전체'); - const { subOption, selectedOptionIdx, setCurrentOptionIdx, setSelectedOptionIdx } = useContext(SubOptionContext); + const { selectedItem, totalPrice, setTotalPrice, setSelectedItem } = useContext(ItemContext); + const handleCategoryClick = (category: string) => { setCurrentCategory(category); }; @@ -29,12 +31,35 @@ export default function SubOptionContainer() { }; const groupedData = groupByCategoryName(subOption); - const handleSelectOption = (index: number) => { + const handleSelectOption = (option: ISubOption) => { + if (!subOption) return; + setSelectedOptionIdx((prevSelectedOptions) => { - if (prevSelectedOptions.includes(index)) { - return prevSelectedOptions.filter((item) => item !== index); + if (prevSelectedOptions.includes(option.subOptionId)) { + setSelectedItem({ + type: 'SET_OPTIONS', + value: selectedItem.options.filter((item) => item.id !== option.subOptionId), + }); + setTotalPrice(totalPrice - option.optionPrice); + + return prevSelectedOptions.filter((item) => item !== option.subOptionId); } else { - return [...prevSelectedOptions, index]; + setSelectedItem({ + type: 'SET_OPTIONS', + value: [ + ...selectedItem.options, + { + id: option.subOptionId, + name: option.optionName, + title: option.optionCategoryName, + imgSrc: option.optionImage, + price: option.optionPrice, + }, + ], + }); + setTotalPrice(totalPrice + option.optionPrice); + + return [...prevSelectedOptions, option.subOptionId]; } }); }; @@ -60,7 +85,7 @@ export default function SubOptionContainer() { price={option.optionPrice} imgPath={option.optionImage} hashTag={option.hashtagName} - handleSelectOption={() => handleSelectOption(option.subOptionId)} + handleSelectOption={() => handleSelectOption(option)} /> {option.hasHmgData && ( diff --git a/frontend/src/context/ItemProvider.tsx b/frontend/src/context/ItemProvider.tsx index 95eb0e8..84b05f3 100644 --- a/frontend/src/context/ItemProvider.tsx +++ b/frontend/src/context/ItemProvider.tsx @@ -75,7 +75,7 @@ const initialSelectedItem = { imgSrc: '', price: 0, }, - options: [{ id: 0, name: '', title: '', imgSrc: '', price: 0 }], + options: [], }; const initialItem: IItemContext = { From 5dd2b40b7addd3c27890d3c8a6220528ffdcf4c2 Mon Sep 17 00:00:00 2001 From: kimdaye77 <63107805+kimdaye77@users.noreply.github.com> Date: Sat, 19 Aug 2023 14:40:03 +0900 Subject: [PATCH 5/7] =?UTF-8?q?[REFACTOR]=20#295:=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20fragment=20=ED=83=9C=EA=B7=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/tabs/OptionTab.tsx | 40 ++++++++-------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/tabs/OptionTab.tsx b/frontend/src/components/tabs/OptionTab.tsx index 9e64a0a..379e100 100644 --- a/frontend/src/components/tabs/OptionTab.tsx +++ b/frontend/src/components/tabs/OptionTab.tsx @@ -1,12 +1,4 @@ -import { - Dispatch, - Fragment, - HTMLAttributes, - SetStateAction, - useEffect, - useRef, - useState, -} from 'react'; +import { Dispatch, HTMLAttributes, SetStateAction, useEffect, useRef, useState } from 'react'; import { BodyKrMedium3, BodyKrRegular3, BodyKrRegular4 } from '../../styles/typefaces'; import styled, { css, useTheme } from 'styled-components'; import { ArrowLeft, ArrowRight } from '../common/icons/Icons'; @@ -100,22 +92,20 @@ export default function OptionTab({ options, setBannerInfo }: ISubOptionTab) { {chunkedOptions.map((optionGroup: ISubOptionList[], groupIndex) => ( - - - {optionGroup.map((option: ISubOptionList, index: number) => ( - - handleOptionClick(index)} - $isselected={page === groupIndex && index === selectedIdx} - > -
{option.optionName}
- {displayUnderline(groupIndex, index)} -
- {option.optionName} -
- ))} -
-
+ + {optionGroup.map((option: ISubOptionList, index: number) => ( + + handleOptionClick(index)} + $isselected={page === groupIndex && index === selectedIdx} + > +
{option.optionName}
+ {displayUnderline(groupIndex, index)} +
+ {option.optionName} +
+ ))} +
))}
From 9b61a0a6847412d9506c91dcc47540818c0834a3 Mon Sep 17 00:00:00 2001 From: jijiseong Date: Sat, 19 Aug 2023 14:42:45 +0900 Subject: [PATCH 6/7] =?UTF-8?q?[REFACTOR]=20#316:=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/buttons/ToggleButtons.tsx | 2 +- frontend/src/components/modal/QuoteSummaryModal.tsx | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/common/buttons/ToggleButtons.tsx b/frontend/src/components/common/buttons/ToggleButtons.tsx index e17fe27..2e7b99c 100644 --- a/frontend/src/components/common/buttons/ToggleButtons.tsx +++ b/frontend/src/components/common/buttons/ToggleButtons.tsx @@ -30,7 +30,7 @@ const Button = styled.button<{ $active: boolean }>` height: 36px; border-radius: 18px; color: ${({ $active, theme }) => ($active ? theme.color.white : theme.color.primaryColor)}; - background-color: ${({ $active, theme }) => ($active ? theme.color.primaryColor : 'none')}; + background-color: ${({ $active, theme }) => ($active ? theme.color.primaryColor : 'transparent')}; `; const ToggleButtonContainer = styled.div` ${flexCenterCss} diff --git a/frontend/src/components/modal/QuoteSummaryModal.tsx b/frontend/src/components/modal/QuoteSummaryModal.tsx index 2e1affa..45ceba2 100644 --- a/frontend/src/components/modal/QuoteSummaryModal.tsx +++ b/frontend/src/components/modal/QuoteSummaryModal.tsx @@ -36,10 +36,9 @@ export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { setVisible(false); }; - const optionNames = ['차량', '옵션', '들의', '임시', '목록', '목록입니다', '목록입니다']; - // const optionNames = selectedItem.options.map((option, idx) => { - // return option.name; - // }); + const optionNames = selectedItem.options.map((option) => { + return option.name; + }); return ( @@ -178,8 +177,7 @@ const DetailName = styled.div` color: ${({ theme }) => theme.color.gray900}; text-align: start; width: 140px; - overflow-x: hidden; - overflow-y: hidden; + overflow: hidden; white-space: nowrap; &::-webkit-scrollbar { display: none; From de2ab6ac6666e6ed33d9c7ec5ed46bf61662e2e0 Mon Sep 17 00:00:00 2001 From: jijiseong Date: Sat, 19 Aug 2023 14:53:27 +0900 Subject: [PATCH 7/7] =?UTF-8?q?[FEAT]=20#316:=20=20=EA=B2=AC=EC=A0=81?= =?UTF-8?q?=EC=9A=94=EC=95=BD=20=EC=98=B5=EC=85=98=20=EA=B0=80=EA=B2=A9=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/modal/QuoteSummaryModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/modal/QuoteSummaryModal.tsx b/frontend/src/components/modal/QuoteSummaryModal.tsx index 45ceba2..dd0a506 100644 --- a/frontend/src/components/modal/QuoteSummaryModal.tsx +++ b/frontend/src/components/modal/QuoteSummaryModal.tsx @@ -39,6 +39,7 @@ export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { const optionNames = selectedItem.options.map((option) => { return option.name; }); + const optionPrice = selectedItem.options.reduce((acc, option) => acc + option.price, 0); return ( @@ -90,7 +91,7 @@ export default function QuoteSummaryModal({ ...props }: IQuoteSummaryModal) { price={selectedItem.innerColor.price} />
- +