From 3d8b81c26766f10edbba6e1f023fd9e7a360eb76 Mon Sep 17 00:00:00 2001 From: sue Date: Fri, 29 Dec 2023 18:38:26 +0900 Subject: [PATCH 01/38] =?UTF-8?q?Feat:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/icons/Icons.tsx | 450 ++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 src/components/common/icons/Icons.tsx diff --git a/src/components/common/icons/Icons.tsx b/src/components/common/icons/Icons.tsx new file mode 100644 index 00000000..ce9d0b3e --- /dev/null +++ b/src/components/common/icons/Icons.tsx @@ -0,0 +1,450 @@ +interface IconProps { + size?: number; + color?: string; + fill?: string; +} + +export const HomeIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + ); +}; + +export const CalendarIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export const HeartIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + ); +}; + +export const UserIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + ); +}; + +export const CheckIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + ); +}; + +export const LeftIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + ); +}; + +export const MapIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + ); +}; + +export const PenIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + + + ); +}; + +export const PhoneIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + ); +}; + +export const ReactIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + ); +}; + +export const StarIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + ); +}; + +export const PlusIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; From 5444bc840dcb0905d5db640bd646634caf9484d1 Mon Sep 17 00:00:00 2001 From: sue Date: Fri, 29 Dec 2023 18:39:32 +0900 Subject: [PATCH 02/38] =?UTF-8?q?Feat:=20=EB=A9=94=EC=9D=B8=20=EB=84=A4?= =?UTF-8?q?=EB=B9=84=EB=B0=94=20=EB=A7=88=ED=81=AC=EC=97=85,=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/footer/Footer.tsx | 55 +++++++++++++++++++++---- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/components/common/footer/Footer.tsx b/src/components/common/footer/Footer.tsx index 2e746898..de056344 100644 --- a/src/components/common/footer/Footer.tsx +++ b/src/components/common/footer/Footer.tsx @@ -1,9 +1,50 @@ -const Footer = () => ( -
- nav bar -
-); +import { useLocation, useNavigate } from 'react-router-dom'; +import { CalendarIcon, HeartIcon, HomeIcon, UserIcon } from '../icons/Icons'; + +const Footer = () => { + const navigate = useNavigate(); + const location = useLocation(); + + const isActive = (path: string) => location.pathname === path; + + return ( +
+ +
+ ); +}; export default Footer; From 53034aa4b64b430679fc12c87d589ba92d0562e9 Mon Sep 17 00:00:00 2001 From: sue Date: Fri, 29 Dec 2023 18:55:43 +0900 Subject: [PATCH 03/38] =?UTF-8?q?Feat:=20=EC=95=84=EC=9D=B4=EC=BD=98?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/icons/Icons.tsx | 265 ++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) diff --git a/src/components/common/icons/Icons.tsx b/src/components/common/icons/Icons.tsx index ce9d0b3e..72459316 100644 --- a/src/components/common/icons/Icons.tsx +++ b/src/components/common/icons/Icons.tsx @@ -448,3 +448,268 @@ export const PlusIcon: React.FC = ({ ); }; + +export const ChatIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + + + + + + + + + ); +}; + +export const CloseIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + ); +}; + +export const DeleteIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + + + + ); +}; + +export const MoreIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + + + + ); +}; + +export const DownIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + ); +}; + +export const SearchIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + + + + ); +}; + +export const CameraIcon: React.FC = ({ + size = 25, + color = 'black', + fill = 'none', +}) => { + return ( + + + + ); +}; From 3756df82b116792cf94f2cc7f2f0df2db797b479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Sat, 30 Dec 2023 18:34:43 +0900 Subject: [PATCH 04/38] =?UTF-8?q?Remove:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=EC=84=B9=EC=85=98=EB=B0=94=ED=85=80=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DetailSectionBottom/A.tsx | 17 +++++++++++++++++ src/components/DetailSectionBottom/B.tsx | 17 +++++++++++++++++ src/components/DetailSectionBottom/C.tsx | 17 +++++++++++++++++ .../DetailSectionBottom/DetailSectionBottom.tsx | 12 ++++++++++++ src/components/DetailSectionBottom/index.tsx | 5 +++++ src/components/abc/A.tsx | 3 --- src/components/abc/B.tsx | 3 --- src/components/abc/index.tsx | 4 ---- src/components/def/C.tsx | 3 --- src/components/def/D.tsx | 3 --- src/components/def/index.tsx | 4 ---- src/pages/abc/abc.page.tsx | 14 -------------- src/pages/detail/detail.page.tsx | 3 ++- src/router/mainRouter.tsx | 2 -- 14 files changed, 70 insertions(+), 37 deletions(-) create mode 100644 src/components/DetailSectionBottom/A.tsx create mode 100644 src/components/DetailSectionBottom/B.tsx create mode 100644 src/components/DetailSectionBottom/C.tsx create mode 100644 src/components/DetailSectionBottom/DetailSectionBottom.tsx create mode 100644 src/components/DetailSectionBottom/index.tsx delete mode 100644 src/components/abc/A.tsx delete mode 100644 src/components/abc/B.tsx delete mode 100644 src/components/abc/index.tsx delete mode 100644 src/components/def/C.tsx delete mode 100644 src/components/def/D.tsx delete mode 100644 src/components/def/index.tsx delete mode 100644 src/pages/abc/abc.page.tsx diff --git a/src/components/DetailSectionBottom/A.tsx b/src/components/DetailSectionBottom/A.tsx new file mode 100644 index 00000000..02d459fb --- /dev/null +++ b/src/components/DetailSectionBottom/A.tsx @@ -0,0 +1,17 @@ +import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; +import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; + +export default function A() { + return ( +
+ + +
+ ); +} diff --git a/src/components/DetailSectionBottom/B.tsx b/src/components/DetailSectionBottom/B.tsx new file mode 100644 index 00000000..e056a3b0 --- /dev/null +++ b/src/components/DetailSectionBottom/B.tsx @@ -0,0 +1,17 @@ +import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; +import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; + +export default function B() { + return ( +
+ + +
+ ); +} diff --git a/src/components/DetailSectionBottom/C.tsx b/src/components/DetailSectionBottom/C.tsx new file mode 100644 index 00000000..ca97236d --- /dev/null +++ b/src/components/DetailSectionBottom/C.tsx @@ -0,0 +1,17 @@ +import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; +import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; + +export default function C() { + return ( +
+ + +
+ ); +} diff --git a/src/components/DetailSectionBottom/DetailSectionBottom.tsx b/src/components/DetailSectionBottom/DetailSectionBottom.tsx new file mode 100644 index 00000000..f94332cf --- /dev/null +++ b/src/components/DetailSectionBottom/DetailSectionBottom.tsx @@ -0,0 +1,12 @@ +import { A, B, C } from '.'; + +// 담당 컴포넌트들 호출하는 컴포넌트(분업 때문에 페이지 느낌으로 나눠봤습니다), API 호출 컴포넌트, +export default function DetailSectionBottom() { + return ( + <> + + + + + ); +} diff --git a/src/components/DetailSectionBottom/index.tsx b/src/components/DetailSectionBottom/index.tsx new file mode 100644 index 00000000..399513bc --- /dev/null +++ b/src/components/DetailSectionBottom/index.tsx @@ -0,0 +1,5 @@ +import A from './A'; +import B from './B'; +import C from './C'; + +export { A, B, C }; diff --git a/src/components/abc/A.tsx b/src/components/abc/A.tsx deleted file mode 100644 index 2c7b9087..00000000 --- a/src/components/abc/A.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function A() { - return
A
; -} diff --git a/src/components/abc/B.tsx b/src/components/abc/B.tsx deleted file mode 100644 index 856cfd77..00000000 --- a/src/components/abc/B.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function B() { - return
B
; -} diff --git a/src/components/abc/index.tsx b/src/components/abc/index.tsx deleted file mode 100644 index 3e935a15..00000000 --- a/src/components/abc/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import A from '@components/abc/A'; -import B from '@components/abc/B'; - -export { A, B }; diff --git a/src/components/def/C.tsx b/src/components/def/C.tsx deleted file mode 100644 index 604abfd4..00000000 --- a/src/components/def/C.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function C() { - return
C
; -} diff --git a/src/components/def/D.tsx b/src/components/def/D.tsx deleted file mode 100644 index 7e0690b5..00000000 --- a/src/components/def/D.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function D() { - return
D
; -} diff --git a/src/components/def/index.tsx b/src/components/def/index.tsx deleted file mode 100644 index 9e8fb509..00000000 --- a/src/components/def/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import C from '@components/def/C'; -import D from '@components/def/D'; - -export { C, D }; diff --git a/src/pages/abc/abc.page.tsx b/src/pages/abc/abc.page.tsx deleted file mode 100644 index 1de578c9..00000000 --- a/src/pages/abc/abc.page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { A } from '@components/abc'; -import { C, D } from '@components/def'; - -const ABC = () => { - return ( -
- ); -}; - -export default ABC; diff --git a/src/pages/detail/detail.page.tsx b/src/pages/detail/detail.page.tsx index 57a571aa..22223af7 100644 --- a/src/pages/detail/detail.page.tsx +++ b/src/pages/detail/detail.page.tsx @@ -1,12 +1,13 @@ import { DetailHeader } from '@components/common/header'; import DetailSectionTop from '@components/DetailSectionTop/DetailSectionTop'; +import DetailSectionBottom from '@components/DetailSectionBottom/DetailSectionBottom'; const DetailTours = () => { return ( <> - {/* 추천 : DetailSectionBottom => 이런 점이 좋았어요, 리뷰 */} + ); }; diff --git a/src/router/mainRouter.tsx b/src/router/mainRouter.tsx index f948b509..7e1d43a2 100644 --- a/src/router/mainRouter.tsx +++ b/src/router/mainRouter.tsx @@ -2,7 +2,6 @@ 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 Detail from '@pages/detail/detail.page'; @@ -25,7 +24,6 @@ const MainRouter = () => { }> } /> } /> - } /> From 08bdb79b5d6bb8a138e2e1cb346af1b0f23ea747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Sat, 30 Dec 2023 20:01:19 +0900 Subject: [PATCH 05/38] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EC=A4=80=EB=B9=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/tours.ts | 1 + src/components/DetailSectionBottom/A.tsx | 16 +--------------- src/components/DetailSectionBottom/B.tsx | 17 ----------------- src/components/DetailSectionBottom/C.tsx | 17 ----------------- .../DetailSectionBottom/DetailReview.tsx | 10 ++++++++++ .../DetailSectionBottom/DetailSectionBottom.tsx | 5 ++--- src/components/DetailSectionBottom/index.tsx | 5 ++--- .../{DetailToutsMap.tsx => DetailToursMap.tsx} | 0 src/components/DetailSectionTop/index.tsx | 2 +- 9 files changed, 17 insertions(+), 56 deletions(-) delete mode 100644 src/components/DetailSectionBottom/B.tsx delete mode 100644 src/components/DetailSectionBottom/C.tsx create mode 100644 src/components/DetailSectionBottom/DetailReview.tsx rename src/components/DetailSectionTop/{DetailToutsMap.tsx => DetailToursMap.tsx} (100%) diff --git a/src/api/tours.ts b/src/api/tours.ts index 5b9c7837..0ff880ee 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -19,6 +19,7 @@ export const getDetailTours = async (tourItemId: number) => { // 여행 상품 리뷰 조회 export const getToursReviews = async (tourItemId: number) => { const res = await client.get(`tours/${tourItemId}/reviews`); + console.log('res', res.data.data.reviewInfos); return res; }; diff --git a/src/components/DetailSectionBottom/A.tsx b/src/components/DetailSectionBottom/A.tsx index 02d459fb..e03f98b2 100644 --- a/src/components/DetailSectionBottom/A.tsx +++ b/src/components/DetailSectionBottom/A.tsx @@ -1,17 +1,3 @@ -import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; -import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; - export default function A() { - return ( -
- - -
- ); + return
이런 점이 좋았어요
; } diff --git a/src/components/DetailSectionBottom/B.tsx b/src/components/DetailSectionBottom/B.tsx deleted file mode 100644 index e056a3b0..00000000 --- a/src/components/DetailSectionBottom/B.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; -import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; - -export default function B() { - return ( -
- - -
- ); -} diff --git a/src/components/DetailSectionBottom/C.tsx b/src/components/DetailSectionBottom/C.tsx deleted file mode 100644 index ca97236d..00000000 --- a/src/components/DetailSectionBottom/C.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactComponent as PenIcon } from '../../assets/images/Pen.svg'; -import { ReactComponent as CalendarIcon } from '../../assets/images/Calendar.svg'; - -export default function C() { - return ( -
- - -
- ); -} diff --git a/src/components/DetailSectionBottom/DetailReview.tsx b/src/components/DetailSectionBottom/DetailReview.tsx new file mode 100644 index 00000000..c74bb349 --- /dev/null +++ b/src/components/DetailSectionBottom/DetailReview.tsx @@ -0,0 +1,10 @@ +import { getToursReviews } from '@api/tours'; +import { useEffect } from 'react'; + +export default function DetailReview() { + const tourItemId = 1; + useEffect(() => { + getToursReviews(tourItemId); + }, []); + return
리뷰
; +} diff --git a/src/components/DetailSectionBottom/DetailSectionBottom.tsx b/src/components/DetailSectionBottom/DetailSectionBottom.tsx index f94332cf..ac4ad43a 100644 --- a/src/components/DetailSectionBottom/DetailSectionBottom.tsx +++ b/src/components/DetailSectionBottom/DetailSectionBottom.tsx @@ -1,12 +1,11 @@ -import { A, B, C } from '.'; +import { A, DetailReview } from '.'; // 담당 컴포넌트들 호출하는 컴포넌트(분업 때문에 페이지 느낌으로 나눠봤습니다), API 호출 컴포넌트, export default function DetailSectionBottom() { return ( <>
- - + ); } diff --git a/src/components/DetailSectionBottom/index.tsx b/src/components/DetailSectionBottom/index.tsx index 399513bc..70f9c29e 100644 --- a/src/components/DetailSectionBottom/index.tsx +++ b/src/components/DetailSectionBottom/index.tsx @@ -1,5 +1,4 @@ import A from './A'; -import B from './B'; -import C from './C'; +import DetailReview from './DetailReview'; -export { A, B, C }; +export { A, DetailReview }; diff --git a/src/components/DetailSectionTop/DetailToutsMap.tsx b/src/components/DetailSectionTop/DetailToursMap.tsx similarity index 100% rename from src/components/DetailSectionTop/DetailToutsMap.tsx rename to src/components/DetailSectionTop/DetailToursMap.tsx diff --git a/src/components/DetailSectionTop/index.tsx b/src/components/DetailSectionTop/index.tsx index cc88b9e9..3d938ea5 100644 --- a/src/components/DetailSectionTop/index.tsx +++ b/src/components/DetailSectionTop/index.tsx @@ -1,6 +1,6 @@ import DetailToursInfo from './DetailToursInfo'; import DetailToursRating from './DetailToursRating'; -import DetailToursMap from './DetailToutsMap'; +import DetailToursMap from './DetailToursMap'; import DetailTourButtons from './DetailTourButtons'; export { From 98918f0ee132b95c9ceb3f2f56183dd7949a2fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Mon, 1 Jan 2024 19:38:51 +0900 Subject: [PATCH 06/38] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A6=AC=EB=B7=B0=20=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + src/api/tours.ts | 3 +- .../2023-12-30T203310.200 (1).json | 325 ++++++++++++++++++ .../DetailSectionBottom/DetailReview.tsx | 62 +++- .../DetailSectionBottom/ReviewItem.tsx | 96 ++++++ 5 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json create mode 100644 src/components/DetailSectionBottom/ReviewItem.tsx diff --git a/package.json b/package.json index 25a5135a..76cf8dbf 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "path": "^0.12.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-infinite-scroller": "^1.2.6", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", "styled-components": "^6.1.3" @@ -27,6 +28,7 @@ "devDependencies": { "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", + "@types/react-infinite-scroller": "^1.2.5", "@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/parser": "^6.14.0", "@vitejs/plugin-react": "^4.2.1", diff --git a/src/api/tours.ts b/src/api/tours.ts index 0ff880ee..646c63ff 100644 --- a/src/api/tours.ts +++ b/src/api/tours.ts @@ -19,7 +19,8 @@ export const getDetailTours = async (tourItemId: number) => { // 여행 상품 리뷰 조회 export const getToursReviews = async (tourItemId: number) => { const res = await client.get(`tours/${tourItemId}/reviews`); - console.log('res', res.data.data.reviewInfos); + console.log('res', res); + console.log('res.data.data.reviewInfos', res.data.data.reviewInfos); return res; }; diff --git a/src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json b/src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json new file mode 100644 index 00000000..6ad3e634 --- /dev/null +++ b/src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json @@ -0,0 +1,325 @@ +{ + "status": 200, + "message": "SUCCESS", + "data": { + "ratingAverage": 4.125, + "reviewTotalCount": 8, + "keywordTotalCount": 38, + "reviewInfos": { + "content": [ + { + "reviewId": 15, + "authorNickname": "익명 사용자1", + "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", + "rating": 4, + "createdTime": "2023-12-30T20:02:06.03406", + "content": "좋은 여행지였습니다.", + "keywords": [ + { + "keywordId": 1, + "content": "깨끗해요", + "type": "ACCOMMODATION_KEYWORD" + }, + { + "keywordId": 2, + "content": "친절해요", + "type": "ACCOMMODATION_KEYWORD" + } + ], + "commentCount": 0 + }, + { + "reviewId": 23, + "authorNickname": "익명 사용자1", + "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", + "rating": 4, + "createdTime": "2023-12-30T20:03:17.978397", + "content": "좋은 여행지였습니다.", + "keywords": [ + { + "keywordId": 1, + "content": "깨끗해요", + "type": "ACCOMMODATION_KEYWORD" + }, + { + "keywordId": 2, + "content": "친절해요", + "type": "ACCOMMODATION_KEYWORD" + } + ], + "commentCount": 0 + }, + { + "reviewId": 24, + "authorNickname": "익명 사용자1", + "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", + "rating": 4, + "createdTime": "2023-12-30T20:03:18.970952", + "content": "좋은 여행지였습니다.", + "keywords": [ + { + "keywordId": 1, + "content": "깨끗해요", + "type": "ACCOMMODATION_KEYWORD" + }, + { + "keywordId": 2, + "content": "친절해요", + "type": "ACCOMMODATION_KEYWORD" + } + ], + "commentCount": 0 + }, + { + "reviewId": 25, + "authorNickname": "익명 사용자1", + "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", + "rating": 4, + "createdTime": "2023-12-30T20:03:20.335173", + "content": "좋은 여행지였습니다.", + "keywords": [ + { + "keywordId": 1, + "content": "깨끗해요", + "type": "ACCOMMODATION_KEYWORD" + }, + { + "keywordId": 2, + "content": "친절해요", + "type": "ACCOMMODATION_KEYWORD" + } + ], + "commentCount": 0 + }, + { + "reviewId": 26, + "authorNickname": "익명 사용자1", + "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", + "rating": 4, + "createdTime": "2023-12-30T20:03:21.924918", + "content": "좋은 여행지였습니다.", + "keywords": [ + { + "keywordId": 1, + "content": "깨끗해요", + "type": "ACCOMMODATION_KEYWORD" + }, + { + "keywordId": 2, + "content": "친절해요", + "type": "ACCOMMODATION_KEYWORD" + } + ], + "commentCount": 0 + } + ], + "pageable": { + "pageNumber": 0, + "pageSize": 20, + "sort": { + "sorted": false, + "empty": true, + "unsorted": true + }, + "offset": 0, + "paged": true, + "unpaged": false + }, + "last": true, + "totalElements": 5, + "totalPages": 1, + "size": 20, + "number": 0, + "sort": { + "sorted": false, + "empty": true, + "unsorted": true + }, + "first": true, + "numberOfElements": 5, + "empty": false + }, + "tourKeywordInfos": [ + { + "keywordId": 1, + "content": "깨끗해요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 5 + }, + { + "keywordId": 2, + "content": "친절해요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 5 + }, + { + "keywordId": 3, + "content": "뷰가 좋아요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 4, + "content": "침구가 좋아요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 5, + "content": "주차하기 편해요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 6, + "content": "냉난방이 잘돼요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 7, + "content": "대중교통이 편해요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 8, + "content": "호캉스하기 좋아요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 9, + "content": "조식이 맛있어요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 10, + "content": "사진 찍기 좋아요", + "type": "ACCOMMODATION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 11, + "content": "음식이 맛있어요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 12, + "content": "친절해요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 13, + "content": "인테리어가 멋져요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 14, + "content": "매장이 청결해요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 15, + "content": "특별한 메뉴가 있어요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 16, + "content": "가성비가 좋아요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 17, + "content": "재료가 신선해요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 18, + "content": "사진찍기 좋아요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 19, + "content": "주차하기 편해요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 20, + "content": "화장실이 깨끗해요", + "type": "DINING_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 21, + "content": "사진이 잘 나와요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 22, + "content": "뷰가 좋아요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 23, + "content": "관리가 잘 되어있어요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 24, + "content": "볼거리가 많아요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 25, + "content": "편의시설이 잘 되어 있어요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 26, + "content": "대중교통이 편해요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 27, + "content": "주차하기 편해요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 28, + "content": "화장실이 깨끗해요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 29, + "content": "가격이 합리적이에요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + }, + { + "keywordId": 30, + "content": "방문객이 많아요", + "type": "ATTRACTION_KEYWORD", + "keywordCount": 1 + } + ] + } +} \ No newline at end of file diff --git a/src/components/DetailSectionBottom/DetailReview.tsx b/src/components/DetailSectionBottom/DetailReview.tsx index c74bb349..0fd10874 100644 --- a/src/components/DetailSectionBottom/DetailReview.tsx +++ b/src/components/DetailSectionBottom/DetailReview.tsx @@ -1,10 +1,64 @@ import { getToursReviews } from '@api/tours'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; +import { useInfiniteQuery } from '@tanstack/react-query'; +import ReviewItem from './ReviewItem'; export default function DetailReview() { + const [reviewDataLength, setReviewDataLength] = useState(0); const tourItemId = 1; + + const { + data: toursReviews, + fetchNextPage, + hasNextPage, + } = useInfiniteQuery({ + queryKey: ['toursReviews'], + queryFn: ({ pageParam }) => getToursReviews(tourItemId), + initialPageParam: 0, + getNextPageParam: (lastPage, allPages, lastPageParam) => { + const lastData = lastPage?.data?.data?.reviewInfos; + return lastData && lastData.length === 4 ? lastPageParam + 1 : undefined; + }, + }); + useEffect(() => { - getToursReviews(tourItemId); - }, []); - return
리뷰
; + if (toursReviews) { + const totalCount = toursReviews.pages.reduce( + (accumulator, page) => + accumulator + (page?.data?.data?.reviewInfos?.length || 0), + 0, + ); + setReviewDataLength(totalCount); + } + }, [toursReviews]); + + return ( + <> +
+ 리뷰 {reviewDataLength} +
+ fetchNextPage()} + initialLoad={false}> + {toursReviews?.pages?.map((page, pageIndex) => ( +
+ {page?.data?.data?.reviewInfos?.map((item: any, index: number) => ( + + ))} +
+ ))} +
+ + ); } diff --git a/src/components/DetailSectionBottom/ReviewItem.tsx b/src/components/DetailSectionBottom/ReviewItem.tsx new file mode 100644 index 00000000..b93cb606 --- /dev/null +++ b/src/components/DetailSectionBottom/ReviewItem.tsx @@ -0,0 +1,96 @@ +import { useEffect } from 'react'; +import { StarIcon, ChatIcon } from '@components/common/icons/Icons'; + +interface Keyword { + keywordId: number; + content: string; + type: string; +} + +interface ItemProps { + authorNickname: string; + authorProfileImageUrl: string; + rating: number; + createdTime: any; + content: string; + keywords: Keyword[]; // keywordId, content, type + commentCount: number; +} + +const Item: React.FC = (props: ItemProps) => { + const { + authorNickname, + authorProfileImageUrl, + rating, + createdTime, + content, + keywords, + commentCount, + } = 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; + }; + useEffect(() => { + console.log('commentCount', commentCount); + }, []); + return ( +
+
+ {/* {authorProfileImageUrl} */} +
+ 유저 프로필 +
+
+
{authorNickname}
+
+ {Array.from({ length: 5 }, (_, index) => ( + + ))} +
+
+
+ {formatCreatedTime(createdTime)} +
+
+
{content}
+
+
+ {keywords.map((keyword, idx) => { + return ( +
+ {keyword.content} +
+ ); + })} +
+
+ +
{commentCount}
+
+
+
+ ); +}; + +export default Item; From dd49ab11a32ef60ae803d786d5e429e718cb0928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=96=B4=EC=8A=B9=EC=A4=80?= Date: Mon, 1 Jan 2024 20:56:46 +0900 Subject: [PATCH 07/38] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A7=88=ED=81=AC?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2023-12-30T203310.200 (1).json | 325 ------------------ src/components/Review/Review.tsx | 15 + src/components/Review/ReviewButton.tsx | 5 + src/components/Review/ReviewKeyword.tsx | 47 +++ src/components/Review/ReviewPosting.tsx | 24 ++ src/components/Review/ReviewRating.tsx | 19 + src/components/Review/index.tsx | 6 + .../postingReview/postingReview.page.tsx | 11 + src/router/mainRouter.tsx | 2 + 9 files changed, 129 insertions(+), 325 deletions(-) delete mode 100644 src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json create mode 100644 src/components/Review/Review.tsx create mode 100644 src/components/Review/ReviewButton.tsx create mode 100644 src/components/Review/ReviewKeyword.tsx create mode 100644 src/components/Review/ReviewPosting.tsx create mode 100644 src/components/Review/ReviewRating.tsx create mode 100644 src/components/Review/index.tsx create mode 100644 src/pages/postingReview/postingReview.page.tsx diff --git a/src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json b/src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json deleted file mode 100644 index 6ad3e634..00000000 --- a/src/components/DetailSectionBottom/2023-12-30T203310.200 (1).json +++ /dev/null @@ -1,325 +0,0 @@ -{ - "status": 200, - "message": "SUCCESS", - "data": { - "ratingAverage": 4.125, - "reviewTotalCount": 8, - "keywordTotalCount": 38, - "reviewInfos": { - "content": [ - { - "reviewId": 15, - "authorNickname": "익명 사용자1", - "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", - "rating": 4, - "createdTime": "2023-12-30T20:02:06.03406", - "content": "좋은 여행지였습니다.", - "keywords": [ - { - "keywordId": 1, - "content": "깨끗해요", - "type": "ACCOMMODATION_KEYWORD" - }, - { - "keywordId": 2, - "content": "친절해요", - "type": "ACCOMMODATION_KEYWORD" - } - ], - "commentCount": 0 - }, - { - "reviewId": 23, - "authorNickname": "익명 사용자1", - "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", - "rating": 4, - "createdTime": "2023-12-30T20:03:17.978397", - "content": "좋은 여행지였습니다.", - "keywords": [ - { - "keywordId": 1, - "content": "깨끗해요", - "type": "ACCOMMODATION_KEYWORD" - }, - { - "keywordId": 2, - "content": "친절해요", - "type": "ACCOMMODATION_KEYWORD" - } - ], - "commentCount": 0 - }, - { - "reviewId": 24, - "authorNickname": "익명 사용자1", - "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", - "rating": 4, - "createdTime": "2023-12-30T20:03:18.970952", - "content": "좋은 여행지였습니다.", - "keywords": [ - { - "keywordId": 1, - "content": "깨끗해요", - "type": "ACCOMMODATION_KEYWORD" - }, - { - "keywordId": 2, - "content": "친절해요", - "type": "ACCOMMODATION_KEYWORD" - } - ], - "commentCount": 0 - }, - { - "reviewId": 25, - "authorNickname": "익명 사용자1", - "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", - "rating": 4, - "createdTime": "2023-12-30T20:03:20.335173", - "content": "좋은 여행지였습니다.", - "keywords": [ - { - "keywordId": 1, - "content": "깨끗해요", - "type": "ACCOMMODATION_KEYWORD" - }, - { - "keywordId": 2, - "content": "친절해요", - "type": "ACCOMMODATION_KEYWORD" - } - ], - "commentCount": 0 - }, - { - "reviewId": 26, - "authorNickname": "익명 사용자1", - "authorProfileImageUrl": "https://common.hanmi.co.kr/upfile/ces/product/p_2011_tenten_p_400.jpg", - "rating": 4, - "createdTime": "2023-12-30T20:03:21.924918", - "content": "좋은 여행지였습니다.", - "keywords": [ - { - "keywordId": 1, - "content": "깨끗해요", - "type": "ACCOMMODATION_KEYWORD" - }, - { - "keywordId": 2, - "content": "친절해요", - "type": "ACCOMMODATION_KEYWORD" - } - ], - "commentCount": 0 - } - ], - "pageable": { - "pageNumber": 0, - "pageSize": 20, - "sort": { - "sorted": false, - "empty": true, - "unsorted": true - }, - "offset": 0, - "paged": true, - "unpaged": false - }, - "last": true, - "totalElements": 5, - "totalPages": 1, - "size": 20, - "number": 0, - "sort": { - "sorted": false, - "empty": true, - "unsorted": true - }, - "first": true, - "numberOfElements": 5, - "empty": false - }, - "tourKeywordInfos": [ - { - "keywordId": 1, - "content": "깨끗해요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 5 - }, - { - "keywordId": 2, - "content": "친절해요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 5 - }, - { - "keywordId": 3, - "content": "뷰가 좋아요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 4, - "content": "침구가 좋아요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 5, - "content": "주차하기 편해요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 6, - "content": "냉난방이 잘돼요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 7, - "content": "대중교통이 편해요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 8, - "content": "호캉스하기 좋아요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 9, - "content": "조식이 맛있어요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 10, - "content": "사진 찍기 좋아요", - "type": "ACCOMMODATION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 11, - "content": "음식이 맛있어요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 12, - "content": "친절해요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 13, - "content": "인테리어가 멋져요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 14, - "content": "매장이 청결해요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 15, - "content": "특별한 메뉴가 있어요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 16, - "content": "가성비가 좋아요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 17, - "content": "재료가 신선해요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 18, - "content": "사진찍기 좋아요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 19, - "content": "주차하기 편해요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 20, - "content": "화장실이 깨끗해요", - "type": "DINING_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 21, - "content": "사진이 잘 나와요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 22, - "content": "뷰가 좋아요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 23, - "content": "관리가 잘 되어있어요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 24, - "content": "볼거리가 많아요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 25, - "content": "편의시설이 잘 되어 있어요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 26, - "content": "대중교통이 편해요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 27, - "content": "주차하기 편해요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 28, - "content": "화장실이 깨끗해요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 29, - "content": "가격이 합리적이에요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - }, - { - "keywordId": 30, - "content": "방문객이 많아요", - "type": "ATTRACTION_KEYWORD", - "keywordCount": 1 - } - ] - } -} \ No newline at end of file diff --git a/src/components/Review/Review.tsx b/src/components/Review/Review.tsx new file mode 100644 index 00000000..6114bb50 --- /dev/null +++ b/src/components/Review/Review.tsx @@ -0,0 +1,15 @@ +import ReviewButton from './ReviewButton'; +import ReviewKeyword from './ReviewKeyword'; +import ReviewPosting from './ReviewPosting'; +import ReviewRating from './ReviewRating'; + +export default function Review() { + return ( + <> + + + + + + ); +} diff --git a/src/components/Review/ReviewButton.tsx b/src/components/Review/ReviewButton.tsx new file mode 100644 index 00000000..eaf9c5a4 --- /dev/null +++ b/src/components/Review/ReviewButton.tsx @@ -0,0 +1,5 @@ +import { ButtonPrimary } from '@components/common/button/Button'; + +export default function ReviewButton() { + return {}}>완료; +} diff --git a/src/components/Review/ReviewKeyword.tsx b/src/components/Review/ReviewKeyword.tsx new file mode 100644 index 00000000..79bb0f5e --- /dev/null +++ b/src/components/Review/ReviewKeyword.tsx @@ -0,0 +1,47 @@ +export default function ReviewKeyword() { + return ( +
+
어떤 점이 좋았나요?
+
+
+
+ 깨끗해요 +
+
+ 친절해요 +
+
+ 뷰가 좋아요 +
+
+
+
+ 침구가 좋아요 +
+
+ 주차하기 편해요 +
+
+ 냉난방이 잘돼요 +
+
+
+
+ 대중교통이 편해요 +
+
+ 호캉스하기 좋아요 +
+
+
+
+ 조식이 맛있어요 +
+
+ 사진 찍기 좋아요 +
+
+
+
+ ); +} diff --git a/src/components/Review/ReviewPosting.tsx b/src/components/Review/ReviewPosting.tsx new file mode 100644 index 00000000..d50a518c --- /dev/null +++ b/src/components/Review/ReviewPosting.tsx @@ -0,0 +1,24 @@ +import { useState, ChangeEvent } from 'react'; + +export default function ReviewPosting() { + const [textLength, setTextLength] = useState(0); + + const handleTextChange = (event: ChangeEvent) => { + const inputText = event.target.value; + setTextLength(inputText.length); + }; + + return ( +
+
리뷰를 작성해주세요
+
+