From 80c35a927f96a92dd9d6e57416d6819eb929e7ee Mon Sep 17 00:00:00 2001 From: joanShim Date: Fri, 29 Dec 2023 21:50:05 +0900 Subject: [PATCH 01/46] =?UTF-8?q?Feat:=20=EB=8B=A4=EC=A4=91=ED=83=9D?= =?UTF-8?q?=EC=9D=BC(=EB=9D=BC=EB=94=94=EC=98=A4)=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 129 +++++++++++++++++++++++ src/components/common/radio/Radio.tsx | 85 +++++++++++++++ src/components/search/RegionSelect.tsx | 19 ++++ src/components/search/SearchInput.tsx | 14 +++ src/components/search/StartSearchBtn.tsx | 18 ++++ src/constants.ts | 19 ++++ src/main.tsx | 2 +- src/pages/main/main.page.tsx | 9 +- src/pages/search/search.page.tsx | 15 +++ src/router/mainRouter.tsx | 4 +- 11 files changed, 306 insertions(+), 9 deletions(-) create mode 100644 src/components/common/radio/Radio.tsx create mode 100644 src/components/search/RegionSelect.tsx create mode 100644 src/components/search/SearchInput.tsx create mode 100644 src/components/search/StartSearchBtn.tsx create mode 100644 src/constants.ts create mode 100644 src/pages/search/search.page.tsx diff --git a/package.json b/package.json index 25a5135a..8bfcda9e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@pnpm-monorepo/shared": "^1.0.0", "@radix-ui/react-collapsible": "^1.0.3", + "@radix-ui/react-radio-group": "^1.1.3", "@svgr/rollup": "^8.1.0", "@tanstack/react-query": "^5.14.6", "@tanstack/react-query-devtools": "^5.14.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c09288d..30980d54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@radix-ui/react-collapsible': specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) '@svgr/rollup': specifier: ^8.1.0 version: 8.1.0(typescript@5.3.3) @@ -1797,6 +1800,30 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -1825,6 +1852,20 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@types/react': 18.2.45 + react: 18.2.0 + dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -1883,6 +1924,65 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-radio-group@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + '@types/react-dom': 18.2.18 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-slot@1.0.2(@types/react@18.2.45)(react@18.2.0): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -1941,6 +2041,35 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-previous@1.0.1(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@types/react': 18.2.45 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.23.6 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + react: 18.2.0 + dev: false + /@remix-run/router@1.14.1: resolution: {integrity: sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==} engines: {node: '>=14.0.0'} diff --git a/src/components/common/radio/Radio.tsx b/src/components/common/radio/Radio.tsx new file mode 100644 index 00000000..fac7cc86 --- /dev/null +++ b/src/components/common/radio/Radio.tsx @@ -0,0 +1,85 @@ +import * as RadioGroup from '@radix-ui/react-radio-group'; +import { useState } from 'react'; + +interface RadioSelectProps { + items: (string | number)[] | Record; + ariaLabel: string; + onSelectionChange: (selectedValue: string | number) => void; + gridCols?: number; +} + +type gridColumnsType = { + [key: number]: string; +}; + +const gridColumns: gridColumnsType = { + 2: 'grid grid-cols-2', + 3: 'grid grid-cols-3', + 4: 'grid grid-cols-4', +}; + +export const RadioSelect: React.FC = ({ + items, + ariaLabel, + onSelectionChange, + gridCols = 2, +}) => { + const [selectedValue, setSelectedValue] = useState( + null, + ); + + console.log('selected Radio value:', selectedValue); + + const handleRadioChange = (value: string | number) => { + setSelectedValue(value); + onSelectionChange(value); + }; + + const gridClassName = gridColumns[gridCols] || gridColumns[2]; + return ( + <> +
+ + {Array.isArray(items) + ? items.map((value) => ( +
+ + + + +
+ )) + : Object.entries(items).map(([label, value]) => ( +
+ + + + +
+ ))} +
+
+ + ); +}; diff --git a/src/components/search/RegionSelect.tsx b/src/components/search/RegionSelect.tsx new file mode 100644 index 00000000..ad8847d7 --- /dev/null +++ b/src/components/search/RegionSelect.tsx @@ -0,0 +1,19 @@ +import { AREA_CODE } from '@/constants'; +import { RadioSelect } from '@components/common/radio/Radio'; + +interface RegionSelectProps { + onRegionChange: (selectedRegion: string | number) => void; +} + +export const RegionSelect: React.FC = ({ + onRegionChange, +}) => { + return ( + + ); +}; diff --git a/src/components/search/SearchInput.tsx b/src/components/search/SearchInput.tsx new file mode 100644 index 00000000..c70b9124 --- /dev/null +++ b/src/components/search/SearchInput.tsx @@ -0,0 +1,14 @@ +export const SearchInput = () => { + return ( + <> +
+ + ); +}; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..5a59b740 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,19 @@ +export const AREA_CODE = { + 서울: '서울', + 부산: '부산', + 대구: '대구', + 인천: '인천', + 광주: '광주', + 대전: '대전', + 울산: '울산', + 세종: '세종', + 제주: '제주', + 경기: '경기', + 강원: '강원', + 충북: '충북', + 충남: '충남', + 경북: '경북', + 경남: '경남', + 전북: '전북', + 전남: '전남', +}; diff --git a/src/main.tsx b/src/main.tsx index 6b0cfa2d..1cd4ba93 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,7 +9,7 @@ import './index.css'; // } if (import.meta.env.DEV) { const { worker } = await import('./mocks/browser.ts'); - await worker.start(); + await worker.stop(); } ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/src/pages/main/main.page.tsx b/src/pages/main/main.page.tsx index 13769e54..fad5fda6 100644 --- a/src/pages/main/main.page.tsx +++ b/src/pages/main/main.page.tsx @@ -1,8 +1,7 @@ -import { ButtonPrimary, ButtonWhite } from '@components/common/button/Button'; - import { getPopularTours } from '@api/region'; -import { getMember } from '@api/member'; +// import { getMember } from '@api/member'; import { useEffect } from 'react'; +import { StartSearchButton } from '@components/search/StartSearchBtn'; const Main = () => { useEffect(() => { @@ -20,9 +19,7 @@ const Main = () => { return ( <> -

지금 인기여행지

- {}}>더보기 - {}}>완료 + ); }; diff --git a/src/pages/search/search.page.tsx b/src/pages/search/search.page.tsx new file mode 100644 index 00000000..715b134f --- /dev/null +++ b/src/pages/search/search.page.tsx @@ -0,0 +1,15 @@ +import { RegionSelect } from '@components/search/RegionSelect'; +import { SearchInput } from '@components/search/SearchInput'; + +export const Search = () => { + const handleRegionChange = (selectedRegion: string | number) => { + console.log('선택한 지역:', selectedRegion); + }; + + return ( + <> + + + + ); +}; diff --git a/src/router/mainRouter.tsx b/src/router/mainRouter.tsx index 9bf5ad4d..b1607478 100644 --- a/src/router/mainRouter.tsx +++ b/src/router/mainRouter.tsx @@ -2,8 +2,8 @@ import { Outlet, Route, Routes } from 'react-router-dom'; import styled from 'styled-components'; import { Header } from '@components/common/header'; import { Footer } from '@components/common/footer'; -import ABC from '@pages/abc/abc.page'; import Main from '@pages/main/main.page'; +import { Search } from '@pages/search/search.page'; export function MainLayout() { return ( @@ -23,7 +23,7 @@ const MainRouter = () => { }> } /> - } /> + } /> From 30bb6b8947596c42322284c4d7e0416b306c7e3a Mon Sep 17 00:00:00 2001 From: joanShim Date: Fri, 29 Dec 2023 22:04:35 +0900 Subject: [PATCH 02/46] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=EC=98=81?= =?UTF-8?q?=EC=97=AD=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/Search.svg | 6 ++++++ src/components/search/SearchInput.tsx | 15 ++++++++++++--- src/components/search/StartSearchBtn.tsx | 5 +++-- 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/assets/images/Search.svg diff --git a/src/assets/images/Search.svg b/src/assets/images/Search.svg new file mode 100644 index 00000000..d8e513bb --- /dev/null +++ b/src/assets/images/Search.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/search/SearchInput.tsx b/src/components/search/SearchInput.tsx index c70b9124..a667d5bc 100644 --- a/src/components/search/SearchInput.tsx +++ b/src/components/search/SearchInput.tsx @@ -1,11 +1,20 @@ +import { useNavigate } from 'react-router-dom'; +import { ReactComponent as LeftIcon } from '@assets/images/Left.svg'; +import { ReactComponent as SearchIcon } from '@assets/images/Search.svg'; + export const SearchInput = () => { + const navigate = useNavigate(); + + const goBack = () => { + navigate(-1); + }; return ( <>
- ); }; From a1d9bb8028773ee3ce462605cbac4852069d05b0 Mon Sep 17 00:00:00 2001 From: joanShim Date: Fri, 29 Dec 2023 23:37:39 +0900 Subject: [PATCH 03/46] =?UTF-8?q?Feat:=20=EA=B2=80=EC=83=89=EA=B0=92=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A1=B0=EA=B1=B4=EB=B6=80=20?= =?UTF-8?q?=EB=A0=8C=EB=8D=94=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/DeleteInput.svg | 5 +++ src/components/search/ResultCategory.tsx | 15 +++++++++ src/components/search/ResultItem.tsx | 17 ++++++++++ src/components/search/SearchInput.tsx | 42 ++++++++++++++++++++++-- src/components/search/SearchResult.tsx | 11 +++++++ src/pages/search/search.page.tsx | 22 +++++++++++-- 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 src/assets/images/DeleteInput.svg create mode 100644 src/components/search/ResultCategory.tsx create mode 100644 src/components/search/ResultItem.tsx create mode 100644 src/components/search/SearchResult.tsx diff --git a/src/assets/images/DeleteInput.svg b/src/assets/images/DeleteInput.svg new file mode 100644 index 00000000..b1020d4f --- /dev/null +++ b/src/assets/images/DeleteInput.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/search/ResultCategory.tsx b/src/components/search/ResultCategory.tsx new file mode 100644 index 00000000..0ceddfa0 --- /dev/null +++ b/src/components/search/ResultCategory.tsx @@ -0,0 +1,15 @@ +import { ButtonWhite } from '@components/common/button/Button'; +import { ResultItem } from './ResultItem'; + +export const ResultCategory = () => { + return ( + <> +

숙소

+ + + {}}> + 숙소 더 보기 + + + ); +}; diff --git a/src/components/search/ResultItem.tsx b/src/components/search/ResultItem.tsx new file mode 100644 index 00000000..711268e7 --- /dev/null +++ b/src/components/search/ResultItem.tsx @@ -0,0 +1,17 @@ +export const ResultItem = () => { + return ( +
+
+ +
+
+
강릉 세인트존스 호텔
+ 강원 강릉시 창해로 307 +
+
+ ); +}; diff --git a/src/components/search/SearchInput.tsx b/src/components/search/SearchInput.tsx index a667d5bc..613b94c9 100644 --- a/src/components/search/SearchInput.tsx +++ b/src/components/search/SearchInput.tsx @@ -1,22 +1,58 @@ import { useNavigate } from 'react-router-dom'; import { ReactComponent as LeftIcon } from '@assets/images/Left.svg'; import { ReactComponent as SearchIcon } from '@assets/images/Search.svg'; +import { ReactComponent as CloseIcon } from '@assets/images/DeleteInput.svg'; +import { useEffect, useState } from 'react'; -export const SearchInput = () => { +interface SearchInputProps { + onInputChange: (value: string) => void; + selectedRegion: string | number; +} + +export const SearchInput: React.FC = ({ + onInputChange, + selectedRegion, +}) => { + const [inputValue, setInputValue] = useState(''); const navigate = useNavigate(); const goBack = () => { navigate(-1); }; + + const clearInput = () => { + setInputValue(''); + onInputChange(''); + }; + + useEffect(() => { + setInputValue(selectedRegion.toString()); + }, [selectedRegion]); + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setInputValue(value); + onInputChange(value); + }; + console.log(inputValue); return ( <> -
+
{}} - placeholder="어디로 떠나세요?"> + placeholder="어디로 떠나세요?" + value={inputValue} + onChange={handleChange} + /> + {inputValue && ( + + )}
); diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx new file mode 100644 index 00000000..ee298c70 --- /dev/null +++ b/src/components/search/SearchResult.tsx @@ -0,0 +1,11 @@ +import { ResultCategory } from './ResultCategory'; + +export const SearchResult = () => { + return ( + <> +
전체 숙소 식당 관광지
+ + + + ); +}; diff --git a/src/pages/search/search.page.tsx b/src/pages/search/search.page.tsx index 715b134f..f05ff288 100644 --- a/src/pages/search/search.page.tsx +++ b/src/pages/search/search.page.tsx @@ -1,15 +1,33 @@ import { RegionSelect } from '@components/search/RegionSelect'; import { SearchInput } from '@components/search/SearchInput'; +import { SearchResult } from '@components/search/SearchResult'; +import { useState } from 'react'; export const Search = () => { + const [searchValue, setSearchValue] = useState(''); + const [selectedRegion, setSelectedRegion] = useState(''); + const handleRegionChange = (selectedRegion: string | number) => { console.log('선택한 지역:', selectedRegion); + setSelectedRegion(selectedRegion.toString()); + setSearchValue(selectedRegion.toString()); + }; + + const handleInputChange = (value: string) => { + setSearchValue(value); }; return ( <> - - + + {searchValue ? ( + + ) : ( + + )} ); }; From 1505b2ef323a1514228504e1f0cb4ebc9725ffd0 Mon Sep 17 00:00:00 2001 From: joanShim Date: Sun, 31 Dec 2023 14:47:56 +0900 Subject: [PATCH 04/46] =?UTF-8?q?Design:=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20?= =?UTF-8?q?=EC=B9=BC=EB=9F=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/search/RegionSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/search/RegionSelect.tsx b/src/components/search/RegionSelect.tsx index ad8847d7..3d2de683 100644 --- a/src/components/search/RegionSelect.tsx +++ b/src/components/search/RegionSelect.tsx @@ -13,7 +13,7 @@ export const RegionSelect: React.FC = ({ items={AREA_CODE} ariaLabel="지역선택" onSelectionChange={onRegionChange} - gridCols={3} + // gridCols={3} /> ); }; From ea4e20be7e9ef7ff279946e78fa37fb923671ac0 Mon Sep 17 00:00:00 2001 From: joanShim Date: Tue, 2 Jan 2024 19:24:10 +0900 Subject: [PATCH 05/46] =?UTF-8?q?Feat:=20=EC=A7=80=EC=97=AD=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EA=B0=92=20=EC=BF=BC=EB=A6=AC=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/tours.ts | 6 +-- src/components/common/radio/Radio.tsx | 8 ++- src/components/search/RegionSelect.tsx | 2 +- src/components/search/SearchInput.tsx | 6 +-- src/components/search/SearchResult.tsx | 6 ++- src/constants.ts | 2 +- src/pages/search/search.page.tsx | 71 +++++++++++++++++++++++--- 7 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/api/tours.ts b/src/api/tours.ts index 5b9c7837..dfe658d1 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -24,11 +24,11 @@ export const getToursReviews = async (tourItemId: number) => { // 여행지 검색 export const getToursSearch = async ( - region: number, - category: number, + region: string, searchWord: string, - page: number, + page: number = 0, size: number, + category?: string, ) => { const res = await client.get( `tours/search?region=${region}&category=${category}&searchWord=${searchWord}&page=${page}&size=${size}}`, diff --git a/src/components/common/radio/Radio.tsx b/src/components/common/radio/Radio.tsx index fac7cc86..1c93442b 100644 --- a/src/components/common/radio/Radio.tsx +++ b/src/components/common/radio/Radio.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; interface RadioSelectProps { items: (string | number)[] | Record; ariaLabel: string; - onSelectionChange: (selectedValue: string | number) => void; + onSelectionChange: (selectedValue: string) => void; gridCols?: number; } @@ -24,13 +24,11 @@ export const RadioSelect: React.FC = ({ onSelectionChange, gridCols = 2, }) => { - const [selectedValue, setSelectedValue] = useState( - null, - ); + const [selectedValue, setSelectedValue] = useState(null); console.log('selected Radio value:', selectedValue); - const handleRadioChange = (value: string | number) => { + const handleRadioChange = (value: string) => { setSelectedValue(value); onSelectionChange(value); }; diff --git a/src/components/search/RegionSelect.tsx b/src/components/search/RegionSelect.tsx index 3d2de683..5bdebea1 100644 --- a/src/components/search/RegionSelect.tsx +++ b/src/components/search/RegionSelect.tsx @@ -2,7 +2,7 @@ import { AREA_CODE } from '@/constants'; import { RadioSelect } from '@components/common/radio/Radio'; interface RegionSelectProps { - onRegionChange: (selectedRegion: string | number) => void; + onRegionChange: (selectedRegion: string) => void; } export const RegionSelect: React.FC = ({ diff --git a/src/components/search/SearchInput.tsx b/src/components/search/SearchInput.tsx index 613b94c9..5d4945f9 100644 --- a/src/components/search/SearchInput.tsx +++ b/src/components/search/SearchInput.tsx @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'; interface SearchInputProps { onInputChange: (value: string) => void; - selectedRegion: string | number; + selectedRegion: string; } export const SearchInput: React.FC = ({ @@ -26,7 +26,7 @@ export const SearchInput: React.FC = ({ }; useEffect(() => { - setInputValue(selectedRegion.toString()); + setInputValue(selectedRegion); }, [selectedRegion]); const handleChange = (e: React.ChangeEvent) => { @@ -34,7 +34,7 @@ export const SearchInput: React.FC = ({ setInputValue(value); onInputChange(value); }; - console.log(inputValue); + return ( <>
diff --git a/src/components/search/SearchResult.tsx b/src/components/search/SearchResult.tsx index ee298c70..80dab1b5 100644 --- a/src/components/search/SearchResult.tsx +++ b/src/components/search/SearchResult.tsx @@ -1,9 +1,13 @@ import { ResultCategory } from './ResultCategory'; +interface SearchResultProps { + selectedRegion: string; +} -export const SearchResult = () => { +export const SearchResult = ({ selectedRegion }: SearchResultProps) => { return ( <>
전체 숙소 식당 관광지
+
{selectedRegion}
diff --git a/src/constants.ts b/src/constants.ts index 5a59b740..37e408d4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,8 @@ export const AREA_CODE = { 서울: '서울', + 인천: '인천', 부산: '부산', 대구: '대구', - 인천: '인천', 광주: '광주', 대전: '대전', 울산: '울산', diff --git a/src/pages/search/search.page.tsx b/src/pages/search/search.page.tsx index f05ff288..97b60f0c 100644 --- a/src/pages/search/search.page.tsx +++ b/src/pages/search/search.page.tsx @@ -1,30 +1,89 @@ import { RegionSelect } from '@components/search/RegionSelect'; import { SearchInput } from '@components/search/SearchInput'; import { SearchResult } from '@components/search/SearchResult'; -import { useState } from 'react'; +import { useInfiniteQuery } from '@tanstack/react-query'; +import { getToursSearch } from '@api/tours'; +import { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +interface Tour { + id: number; + title: string; + ratingAverage: number; + reviewCount: number; + likedCount: number; + liked: boolean; + smallThumbnailUrl: string; +} + +interface ToursResponse { + totalElements: number; + totalPages: number; + size: number; + content: Tour[]; +} export const Search = () => { const [searchValue, setSearchValue] = useState(''); const [selectedRegion, setSelectedRegion] = useState(''); + const navigate = useNavigate(); + const location = useLocation(); + + const queryParams = new URLSearchParams(location.search); + const regionFromQuery = queryParams.get('region'); + useEffect(() => { + if (regionFromQuery) { + setSelectedRegion(regionFromQuery); + } else { + setSelectedRegion(''); + } + }, [location]); - const handleRegionChange = (selectedRegion: string | number) => { + const handleRegionChange = (selectedRegion: string) => { console.log('선택한 지역:', selectedRegion); - setSelectedRegion(selectedRegion.toString()); - setSearchValue(selectedRegion.toString()); + setSelectedRegion(selectedRegion); + setSearchValue(selectedRegion); + navigate(`?region=${encodeURIComponent(selectedRegion)}`); + console.log('selected region:', searchValue); }; const handleInputChange = (value: string) => { setSearchValue(value); }; + // const { + // data, + // fetchNextPage, + // hasNextPage, + // isFetchingNextPage, + // isError, + // isLoading, + // } = useInfiniteQuery({ + // queryKey: ['toursSearch', selectedRegion, searchValue], + // queryFn: ({ pageParam = 0 }) => + // getToursSearch(selectedRegion, searchValue, pageParam, 10, '').then( + // (response) => response.data.data.content, + // ), + // initialPageParam: 0, + // getNextPageParam: (lastPage, allPages) => { + // const nextPage = allPages.length; + // return nextPage < lastPage.totalPages ? nextPage : undefined; + // }, + // }); + + // if (isLoading) return
Loading...
; + // if (isError) return
Error occurred
; + + // console.log(data); + return ( <> - {searchValue ? ( - + {selectedRegion || searchValue ? ( + ) : ( )} From 4e1a3c00c6d2ae53bebc327068351dc85938af58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Wed, 3 Jan 2024 04:35:01 +0900 Subject: [PATCH 06/46] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0/=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EB=A7=88=ED=81=AC=EC=97=85=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99(READ=EB=A7=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +- src/api/review.ts | 4 +- src/api/tours.ts | 2 - .../{DetailReview.tsx => DetailReviews.tsx} | 37 ++++-- .../DetailSectionBottom.tsx | 15 ++- .../DetailSectionBottom/ReviewItem.tsx | 13 +- src/components/DetailSectionBottom/index.tsx | 4 +- .../DetailSectionTop/DetailSectionTop.tsx | 3 +- .../DetailSectionTop/DetailTourButtons.tsx | 25 +++- src/components/Review/CommentItem.tsx | 60 +++++++++ src/components/Review/DetailReview.tsx | 51 ++++++++ src/components/Review/InputComment.tsx | 30 +++++ src/components/Review/ReviewComments.tsx | 51 ++++++++ src/components/Review/ReviewKeyword.tsx | 123 ++++++++++++------ src/components/Review/ReviewPosting.tsx | 4 +- src/components/Review/ReviewRating.tsx | 10 +- .../Review/UpdateDelete.tsx} | 0 src/components/Review/index.tsx | 13 +- src/components/common/footer/index.tsx | 1 + src/components/common/modal/Modal.tsx | 56 ++++++++ src/components/common/modal/index.tsx | 3 + src/main.tsx | 9 +- .../reviewComment/reviewComment.page.tsx | 14 ++ .../reviewPosting.page.tsx} | 4 +- src/recoil/abc.ts | 1 - src/recoil/modal.ts | 11 ++ src/router/mainRouter.tsx | 12 +- tailwind.config.js | 1 + 28 files changed, 473 insertions(+), 88 deletions(-) rename src/components/DetailSectionBottom/{DetailReview.tsx => DetailReviews.tsx} (73%) create mode 100644 src/components/Review/CommentItem.tsx create mode 100644 src/components/Review/DetailReview.tsx create mode 100644 src/components/Review/InputComment.tsx create mode 100644 src/components/Review/ReviewComments.tsx rename src/{hooks/abc.ts => components/Review/UpdateDelete.tsx} (100%) create mode 100644 src/components/common/modal/Modal.tsx create mode 100644 src/components/common/modal/index.tsx create mode 100644 src/pages/reviewComment/reviewComment.page.tsx rename src/pages/{postingReview/postingReview.page.tsx => reviewPosting/reviewPosting.page.tsx} (75%) delete mode 100644 src/recoil/abc.ts create mode 100644 src/recoil/modal.ts diff --git a/package.json b/package.json index f17ca5a5..6355d78b 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,9 @@ "path": "^0.12.7", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-kakao-maps-sdk": "^1.1.24", "react-infinite-scroller": "^1.2.6", + "react-kakao-maps-sdk": "^1.1.24", + "react-modal": "^3.16.1", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", "styled-components": "^6.1.3" @@ -30,6 +31,7 @@ "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", "@types/react-infinite-scroller": "^1.2.5", + "@types/react-modal": "^3.16.3", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/src/api/review.ts b/src/api/review.ts index a914f451..be1721d1 100644 --- a/src/api/review.ts +++ b/src/api/review.ts @@ -31,7 +31,7 @@ export const getReviewComments = async (reviewId: number) => { }; // 리뷰키워드조회 -export const getReviewKeywords = async (code: number) => { - const res = await client.get(`reviews/keywords?code=${code}`); +export const getReviewKeywords = async (keywordType: string) => { + const res = await client.get(`reviews/keywords?keywordType=${keywordType}`); return res; }; diff --git a/src/api/tours.ts b/src/api/tours.ts index 5c953d25..888e7b7c 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -25,8 +25,6 @@ export const getDetailTours = async (tourItemId: number) => { // 여행 상품 리뷰 조회 export const getToursReviews = async (tourItemId: number) => { const res = await client.get(`tours/${tourItemId}/reviews`); - console.log('res', res); - console.log('res.data.data.reviewInfos', res.data.data.reviewInfos); return res; }; diff --git a/src/components/DetailSectionBottom/DetailReview.tsx b/src/components/DetailSectionBottom/DetailReviews.tsx similarity index 73% rename from src/components/DetailSectionBottom/DetailReview.tsx rename to src/components/DetailSectionBottom/DetailReviews.tsx index b256bfe2..820804e3 100644 --- a/src/components/DetailSectionBottom/DetailReview.tsx +++ b/src/components/DetailSectionBottom/DetailReviews.tsx @@ -4,10 +4,18 @@ import InfiniteScroll from 'react-infinite-scroller'; import { useInfiniteQuery } from '@tanstack/react-query'; import ReviewItem from './ReviewItem'; import { StarIcon } from '@components/common/icons/Icons'; +import { useNavigate, useParams } from 'react-router-dom'; -export default function DetailReview() { +interface reviewProps { + reviewData: any; +} + +export default function DetailReviews({ reviewData }: reviewProps) { const [reviewDataLength, setReviewDataLength] = useState(0); - const tourItemId = 1; + const { title, contentTypeId } = reviewData; + const params = useParams(); + const tourId = Number(params.id); + const navigate = useNavigate(); const { data: toursReviews, @@ -15,7 +23,7 @@ export default function DetailReview() { hasNextPage, } = useInfiniteQuery({ queryKey: ['toursReviews'], - queryFn: ({ pageParam }) => getToursReviews(tourItemId), + queryFn: ({ pageParam }) => getToursReviews(tourId), initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam) => { const lastData = lastPage?.data?.data?.reviewInfos; @@ -23,6 +31,16 @@ export default function DetailReview() { }, }); + const handleReviewClick = (id: number) => { + navigate(`/reviewComment/${id}`); + }; + + const handlePostingReivew = () => { + navigate(`/reviewPosting/${tourId}`, { + state: { title, contentTypeId }, + }); + }; + useEffect(() => { if (toursReviews) { const totalCount = toursReviews.pages.reduce( @@ -57,6 +75,7 @@ export default function DetailReview() { content={item.content} keywords={item.keywords} // keywordId, content, type commentCount={2} //commentCount가 swagger에는 있는데 response에는 없음 + onClick={() => handleReviewClick(item.reviewId)} /> ), )} @@ -65,16 +84,12 @@ export default function DetailReview() { )} {reviewDataLength == 0 && ( -
+
{Array.from({ length: 5 }, (_, index) => ( - + ))}
첫번째 리뷰를 남겨주세요!
diff --git a/src/components/DetailSectionBottom/DetailSectionBottom.tsx b/src/components/DetailSectionBottom/DetailSectionBottom.tsx index ac4ad43a..3cf900a5 100644 --- a/src/components/DetailSectionBottom/DetailSectionBottom.tsx +++ b/src/components/DetailSectionBottom/DetailSectionBottom.tsx @@ -1,11 +1,20 @@ -import { A, DetailReview } from '.'; +import { A, DetailReviews } from '.'; +import { useParams } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { getDetailTours } from '@api/tours'; -// 담당 컴포넌트들 호출하는 컴포넌트(분업 때문에 페이지 느낌으로 나눠봤습니다), API 호출 컴포넌트, export default function DetailSectionBottom() { + const params = useParams(); + const tourId = Number(params.id); + const { isError, isLoading, isFetching, data } = useQuery({ + queryKey: ['details', tourId], + queryFn: () => getDetailTours(tourId), + }); + return ( <> - + ); } diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx index be5acb77..bf3a5d0f 100644 --- a/src/components/DetailSectionBottom/ReviewItem.tsx +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { StarIcon, ChatIcon } from '@components/common/icons/Icons'; +import { StarIcon, ChatIcon, MoreIcon } from '@components/common/icons/Icons'; interface Keyword { keywordId: number; @@ -15,6 +15,7 @@ interface ItemProps { content: string; keywords: Keyword[]; // keywordId, content, type commentCount: number; + onClick?: () => void; } const Item: React.FC = (props: ItemProps) => { @@ -26,6 +27,7 @@ const Item: React.FC = (props: ItemProps) => { content, keywords, commentCount, + onClick, } = props; const formatCreatedTime = (timeString: string): string => { @@ -42,7 +44,7 @@ const Item: React.FC = (props: ItemProps) => { console.log('commentCount', commentCount); }, []); return ( -
+
{/* {authorProfileImageUrl} */}
@@ -70,6 +72,9 @@ const Item: React.FC = (props: ItemProps) => {
{formatCreatedTime(createdTime)}
+
+ +
{content}
@@ -85,8 +90,8 @@ const Item: React.FC = (props: ItemProps) => { })}
- -
{commentCount}
+ +
{commentCount}
diff --git a/src/components/DetailSectionBottom/index.tsx b/src/components/DetailSectionBottom/index.tsx index 70f9c29e..4bce42e2 100644 --- a/src/components/DetailSectionBottom/index.tsx +++ b/src/components/DetailSectionBottom/index.tsx @@ -1,4 +1,4 @@ import A from './A'; -import DetailReview from './DetailReview'; +import DetailReviews from './DetailReviews'; -export { A, DetailReview }; +export { A, DetailReviews }; diff --git a/src/components/DetailSectionTop/DetailSectionTop.tsx b/src/components/DetailSectionTop/DetailSectionTop.tsx index a663f268..82a49706 100644 --- a/src/components/DetailSectionTop/DetailSectionTop.tsx +++ b/src/components/DetailSectionTop/DetailSectionTop.tsx @@ -10,6 +10,7 @@ import { DetailToursMap, DetailTourButtons, } from '.'; +import { useEffect } from 'react'; export default function DetailSectionTop() { const params = useParams(); @@ -26,7 +27,7 @@ export default function DetailSectionTop() { - + ); } diff --git a/src/components/DetailSectionTop/DetailTourButtons.tsx b/src/components/DetailSectionTop/DetailTourButtons.tsx index 7038e566..fb243a67 100644 --- a/src/components/DetailSectionTop/DetailTourButtons.tsx +++ b/src/components/DetailSectionTop/DetailTourButtons.tsx @@ -1,7 +1,26 @@ import { ReactComponent as PenIcon } from '@assets/images/Pen.svg'; import { ReactComponent as CalendarIcon } from '@assets/images/Calendar.svg'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useEffect } from 'react'; -export default function DetailTourButtons() { +interface reviewProps { + reviewData: any; +} + +export default function DetailTourButtons({ reviewData }: reviewProps) { + const { title, contentTypeId } = reviewData; + const params = useParams(); + const tourId = Number(params.id); + const navigate = useNavigate(); + + const handlePostingReivew = () => { + navigate(`/reviewPosting/${tourId}`, { + state: { title, contentTypeId }, + }); + }; + useEffect(() => { + console.log('contentTypeId', contentTypeId); + }, [contentTypeId]); return (
); diff --git a/src/components/Review/CommentItem.tsx b/src/components/Review/CommentItem.tsx new file mode 100644 index 00000000..8122919e --- /dev/null +++ b/src/components/Review/CommentItem.tsx @@ -0,0 +1,60 @@ +import { MoreIcon } from '@components/common/icons/Icons'; + +interface ItemProps { + authorNickname: string; + authorProfileImageUrl: string; + createdTime: any; + content: string; + onClick?: () => void; +} + +const CommentItem: React.FC = (props: ItemProps) => { + const { + authorNickname, + authorProfileImageUrl, + createdTime, + content, + onClick, + } = props; + + const formatCreatedTime = (timeString: string): string => { + const date = new Date(timeString); + const formattedDate = new Intl.DateTimeFormat('ko-KR', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(date); + + return formattedDate; + }; + + return ( +
+
+ {/* {authorProfileImageUrl} */} +
+ 유저 프로필 +
+
+
{authorNickname}
+
+ {formatCreatedTime(createdTime)} +
+
+
+ +
+
+ +
{content}
+
+ ); +}; + +export default CommentItem; diff --git a/src/components/Review/DetailReview.tsx b/src/components/Review/DetailReview.tsx new file mode 100644 index 00000000..5d50000b --- /dev/null +++ b/src/components/Review/DetailReview.tsx @@ -0,0 +1,51 @@ +import { getToursReviews } from '@api/tours'; +import { useQuery } from '@tanstack/react-query'; +import ReviewItem from '@components/DetailSectionBottom/ReviewItem'; +import { useParams } from 'react-router-dom'; +import { useSetRecoilState, useRecoilState } from 'recoil'; +import { isModalOpenState, titleState } from '@recoil/modal'; +import { Modal } from '@components/common/modal'; + +export default function DetailReview() { + const tourItemId = 1; // 아마도 동적으로 tourItemId를 설정해야 할 것입니다. + const { id } = useParams(); + const [isModalOpen, setIsModalOpen] = useRecoilState(isModalOpenState); + const setTitle = useSetRecoilState(titleState); + + const { data: toursReviews } = useQuery({ + queryKey: ['toursReviews'], + queryFn: () => getToursReviews(tourItemId), + }); + + const selectedReview = toursReviews?.data?.data?.reviewInfos.filter( + (item: any) => item.reviewId.toString() === id, + ); + + const openModal = (title: string) => { + setTitle(title); + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + }; + + return ( +
+ {selectedReview?.map((item: any) => ( + openModal('내 리뷰')} + /> + ))} + +
+ ); +} diff --git a/src/components/Review/InputComment.tsx b/src/components/Review/InputComment.tsx new file mode 100644 index 00000000..0d833006 --- /dev/null +++ b/src/components/Review/InputComment.tsx @@ -0,0 +1,30 @@ +import { ReactNode } from 'react'; +interface InputCommentProps { + onClick?: () => void; + children?: ReactNode; + classNameName?: string; +} + +export const InputComment: React.FC = ({ onClick }) => { + return ( + <> +
+
|
+
+ +
+
+ +
+
+ + ); +}; diff --git a/src/components/Review/ReviewComments.tsx b/src/components/Review/ReviewComments.tsx new file mode 100644 index 00000000..9b31e77b --- /dev/null +++ b/src/components/Review/ReviewComments.tsx @@ -0,0 +1,51 @@ +import { getReviewComments } from '@api/review'; +import { useQuery } from '@tanstack/react-query'; +import { useParams } from 'react-router-dom'; +import CommentItem from './CommentItem'; +import { useSetRecoilState, useRecoilState } from 'recoil'; +import { isModalOpenState, titleState } from '@recoil/modal'; +import { Modal } from '@components/common/modal'; + +export default function ReviewComments() { + const { id } = useParams(); + const [isModalOpen, setIsModalOpen] = useRecoilState(isModalOpenState); + const setTitle = useSetRecoilState(titleState); + + const { data: reviewComments } = useQuery({ + queryKey: ['reviewComments'], + queryFn: () => getReviewComments(Number(id)), + }); + + const openModal = (title: string) => { + setTitle(title); + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + }; + + return ( + <> +
+ 댓글 + + {reviewComments?.data?.data?.comments?.length} + +
+ {reviewComments?.data?.data?.comments?.map((comment: any) => { + return ( + openModal('내 댓글')} + /> + ); + })} + + + ); +} diff --git a/src/components/Review/ReviewKeyword.tsx b/src/components/Review/ReviewKeyword.tsx index 79bb0f5e..301d0005 100644 --- a/src/components/Review/ReviewKeyword.tsx +++ b/src/components/Review/ReviewKeyword.tsx @@ -1,47 +1,86 @@ +import { useEffect, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { getReviewKeywords } from '@api/review'; +import { useQuery } from '@tanstack/react-query'; + +interface Keyword { + keywordId: number; + content: string; +} + export default function ReviewKeyword() { + const location = useLocation(); + const { state } = location; + const { contentTypeId } = state; + const [keywordType, setKeywordType] = useState(''); + const [selectedKeywords, setSelectedKeywords] = useState([]); + + useEffect(() => { + if (contentTypeId === 12) { + setKeywordType('ATTRACTION '); + } else if (contentTypeId === 32) { + setKeywordType('ACCOMMODATION'); + } else if (contentTypeId === 39) { + setKeywordType('DINING '); + } + }, [contentTypeId]); + + const { data: reviewKeywords } = useQuery({ + queryKey: ['reviewKeywords', keywordType], + queryFn: () => getReviewKeywords(keywordType), + }); + + const handleKeywordClick = (keyword: Keyword) => { + const isAlreadySelected = selectedKeywords.some( + (selectedKeyword) => selectedKeyword.keywordId === keyword.keywordId, + ); + if (isAlreadySelected) { + setSelectedKeywords((prevSelectedKeywords) => + prevSelectedKeywords.filter( + (selectedKeyword) => selectedKeyword.keywordId !== keyword.keywordId, + ), + ); + } else { + setSelectedKeywords((prevSelectedKeywords) => [ + ...prevSelectedKeywords, + { keywordId: keyword.keywordId, content: keyword.content }, + ]); + } + }; + + // 5x2 형태로 배치하기 위해 행(row)과 열(column)을 계산 + const rows = 5; + const columns = 2; + return ( -
-
어떤 점이 좋았나요?
-
-
-
- 깨끗해요 -
-
- 친절해요 -
-
- 뷰가 좋아요 -
-
-
-
- 침구가 좋아요 -
-
- 주차하기 편해요 -
-
- 냉난방이 잘돼요 -
-
-
-
- 대중교통이 편해요 -
-
- 호캉스하기 좋아요 -
-
-
-
- 조식이 맛있어요 -
-
- 사진 찍기 좋아요 -
-
+ <> +
어떤 점이 좋았나요?
+
+ {reviewKeywords?.data?.data?.keywords?.map( + (keyword: any, index: number) => { + const row = Math.floor(index / columns) + 1; + const col = (index % columns) + 1; + + const isSelected = selectedKeywords.some( + (selectedKeyword) => + selectedKeyword.keywordId === keyword.keywordId, + ); + + return ( +
handleKeywordClick(keyword)}> + {keyword.content} +
+ ); + }, + )}
-
+ ); } diff --git a/src/components/Review/ReviewPosting.tsx b/src/components/Review/ReviewPosting.tsx index e0a37d4a..64cbd429 100644 --- a/src/components/Review/ReviewPosting.tsx +++ b/src/components/Review/ReviewPosting.tsx @@ -10,10 +10,10 @@ export default function ReviewPosting() { return (
-
리뷰를 작성해주세요
+
리뷰를 작성해주세요