From 44574c6b0d3b1b060144b0a817133afa8a4d502d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 13:15:40 +0900 Subject: [PATCH 001/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=95=88=EB=82=B4=20=EB=B0=8F=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=ED=83=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/address.ts | 10 ++++ src/api/course/index.ts | 8 ++- src/api/course/type.ts | 24 +++++++++ src/api/destination/index.ts | 13 ++++- src/api/destination/type.ts | 18 +++++++ .../InfoAndReviewTab.module.scss | 15 ++++++ .../DetailPage/InfoAndReviewTab/index.tsx | 53 +++++++++++++++++++ src/layout/DetailPageLayout/index.tsx | 6 ++- src/pages/CourseDetailPage/index.tsx | 2 +- 9 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 src/components/domains/DetailPage/InfoAndReviewTab/InfoAndReviewTab.module.scss create mode 100644 src/components/domains/DetailPage/InfoAndReviewTab/index.tsx diff --git a/src/api/address.ts b/src/api/address.ts index 364730b..0a0a20c 100644 --- a/src/api/address.ts +++ b/src/api/address.ts @@ -83,3 +83,13 @@ export const destinationWishAddress = { //delete deleteWish: (id: number) => `/v1/destinationwish/${id}`, }; + +export const courseReviewAddress = { + //get + getCourseReviews: "/v1/coursereview", +}; + +export const destinationReviewAddress = { + //get + getDestinationReviews: "/v1/destinationreview", +}; diff --git a/src/api/course/index.ts b/src/api/course/index.ts index 839f6bd..5a4c941 100644 --- a/src/api/course/index.ts +++ b/src/api/course/index.ts @@ -1,7 +1,7 @@ -import { courseLikeAddress, coursesAddress, courseWishAddress } from "../address"; +import { courseLikeAddress, courseReviewAddress, coursesAddress, courseWishAddress } from "../address"; import { apiRequest } from "../axios"; import { postCourse } from "./register"; -import { Course, CourseId, Courses } from "./type"; +import { Course, CourseId, Courses, getCourseReviewsResponseDto, PagenationOptions } from "./type"; // 코스 목록 조회 export const getCourse = (params: string): Promise => apiRequest("get", `${coursesAddress.getList}?${params}`); @@ -31,3 +31,7 @@ export const addCourseWish = (data: CourseId) => // 코스 찜 취소 export const deleteCourseWish = (id: number) => apiRequest("delete", courseWishAddress.deleteWish(id), null, null, { requireAuth: true }); + +//코스 리뷰 조회 +export const getCourseReviews = (qs: PagenationOptions): Promise => + apiRequest("get", courseReviewAddress.getCourseReviews, null, qs); diff --git a/src/api/course/type.ts b/src/api/course/type.ts index 449039b..28a1c3d 100644 --- a/src/api/course/type.ts +++ b/src/api/course/type.ts @@ -42,3 +42,27 @@ export interface Courses { export interface CourseId { courseId: number; } + +export type PagenationOptions = { + courseId: number; + record?: number; + page?: number; +}; + +export interface courseReview { + courseId: number; + reviewId: number; + nickname: string; + title: string; + description: string; + picture: string; + rating: number; +} + +export interface getCourseReviewsResponseDto { + currentPage: number; + totalPage: number; + pagingSlice: number; + totalContents: number; + contents: courseReview[]; +} diff --git a/src/api/destination/index.ts b/src/api/destination/index.ts index 606cc75..8e29684 100644 --- a/src/api/destination/index.ts +++ b/src/api/destination/index.ts @@ -1,8 +1,15 @@ -import { destinationAddress, destinationLikeAddress, destinationWishAddress } from "../address"; +import { + destinationAddress, + destinationLikeAddress, + destinationReviewAddress, + destinationWishAddress, +} from "../address"; import { apiRequest } from "../axios"; +import { PagenationOptions } from "../course/type"; import { DestinationId, getDestinationResponseDto, + getDestinationReviewsResponseDto, GetDestinationsResponseDto, postDestinationRequestDto, postDestinationResponseDto, @@ -44,3 +51,7 @@ export const addDestinationWish = (data: DestinationId) => // 목적지 찜 취소 export const deleteDestinationWish = (id: number) => apiRequest("delete", destinationWishAddress.deleteWish(id), null, null, { requireAuth: true }); + +//목적지 리뷰 조회 +export const getDestinationReviews = (qs: PagenationOptions): Promise => + apiRequest("get", destinationReviewAddress.getDestinationReviews, null, qs); diff --git a/src/api/destination/type.ts b/src/api/destination/type.ts index e5aefc2..9d81c28 100644 --- a/src/api/destination/type.ts +++ b/src/api/destination/type.ts @@ -98,3 +98,21 @@ export type GetDestinationsResponseDto = { export interface DestinationId { destinationId: number; } + +type DestinationReview = { + destinationId: number; + reviewId: number; + nickname: string; + title: string; + description: string; + picture: string; + rating: number; +}; + +export interface getDestinationReviewsResponseDto { + currentPage: number; + totalPage: number; + pagingSlice: number; + totalContents: number; + contents: DestinationReview[]; +} diff --git a/src/components/domains/DetailPage/InfoAndReviewTab/InfoAndReviewTab.module.scss b/src/components/domains/DetailPage/InfoAndReviewTab/InfoAndReviewTab.module.scss new file mode 100644 index 0000000..907a242 --- /dev/null +++ b/src/components/domains/DetailPage/InfoAndReviewTab/InfoAndReviewTab.module.scss @@ -0,0 +1,15 @@ +.tabs { + @include text-style(1.6, 700, $black30); + @include flexbox(around, center); + width: 100%; + border-bottom: solid 0.1rem $gray20; + padding-bottom: 2rem; + + &-btn { + cursor: pointer; + + &.isSelected { + color: $primary40; + } + } +} diff --git a/src/components/domains/DetailPage/InfoAndReviewTab/index.tsx b/src/components/domains/DetailPage/InfoAndReviewTab/index.tsx new file mode 100644 index 0000000..4a75cca --- /dev/null +++ b/src/components/domains/DetailPage/InfoAndReviewTab/index.tsx @@ -0,0 +1,53 @@ +import { getCourseReviews } from "@/api/course"; +import { getCourseReviewsResponseDto } from "@/api/course/type"; +import { getDestinationReviews } from "@/api/destination"; +import { getDestinationReviewsResponseDto } from "@/api/destination/type"; +import { useQuery } from "@tanstack/react-query"; +import classNames from "classnames/bind"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import styles from "./InfoAndReviewTab.module.scss"; + +const cx = classNames.bind(styles); + +type ReviewsResponse = getCourseReviewsResponseDto | getDestinationReviewsResponseDto; +interface InfoAndReviewTabProps { + type: "course" | "destination"; +} + +const InfoAndReviewTab = ({ type }: InfoAndReviewTabProps) => { + const { id } = useParams(); + const postId = Number(id); + const [tab, setTab] = useState("info"); + + const handleInfoClick = () => { + setTab("info"); + }; + + const handleReviewClick = () => { + setTab("review"); + }; + + const { data } = useQuery({ + queryKey: type === "course" ? ["courseReview", postId] : ["destinationReview", postId], + queryFn: () => + type === "course" ? getCourseReviews({ courseId: postId }) : getDestinationReviews({ courseId: postId }), + }); + + const totalReviewCount = data?.totalContents; + + return ( + <> +
+ + +
+ {tab === "info" ?
안내
:
리뷰
} + + ); +}; +export default InfoAndReviewTab; diff --git a/src/layout/DetailPageLayout/index.tsx b/src/layout/DetailPageLayout/index.tsx index bc840b5..2077d26 100644 --- a/src/layout/DetailPageLayout/index.tsx +++ b/src/layout/DetailPageLayout/index.tsx @@ -1,3 +1,4 @@ +import InfoAndReviewTab from "@/components/domains/DetailPage/InfoAndReviewTab"; import classNames from "classnames/bind"; import { ReactNode } from "react"; import styles from "./DetailPageLayout.module.scss"; @@ -5,16 +6,19 @@ import styles from "./DetailPageLayout.module.scss"; const cx = classNames.bind(styles); interface DetailPageLayoutProps { + type: "course" | "destination"; header: ReactNode; main: ReactNode; // info: ReactNode; + // review: ReactNode; } -const DetailPageLayout = ({ header, main }: DetailPageLayoutProps) => { +const DetailPageLayout = ({ type, header, main }: DetailPageLayoutProps) => { return (
{header}
{main}
+
); }; diff --git a/src/pages/CourseDetailPage/index.tsx b/src/pages/CourseDetailPage/index.tsx index 7e3de4b..4dd9b22 100644 --- a/src/pages/CourseDetailPage/index.tsx +++ b/src/pages/CourseDetailPage/index.tsx @@ -3,7 +3,7 @@ import CourseHeader from "@/components/domains/DetailPage/CourseDetail/Header/Co import DetailPageLayout from "@/layout/DetailPageLayout"; const CourseDetailPage = () => { - return } main={} />; + return } main={} />; }; export default CourseDetailPage; From 40bf7e2b14f8fcb87a1bbefea69007a205eb1f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 13:43:05 +0900 Subject: [PATCH 002/131] =?UTF-8?q?Refactor:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/course_info_calendar.svg | 4 ++++ src/assets/images/course_info_people.svg | 4 ++++ .../{DetailPage => detail}/Header/Header.module.scss | 0 .../domains/{DetailPage => detail}/Header/Header.tsx | 0 .../InfoAndReviewTab/InfoAndReviewTab.module.scss | 0 .../InfoAndReviewTab/index.tsx | 0 .../course/CourseHeader}/CourseHeader.tsx | 0 .../course/CourseMain/CourseMain.module.scss} | 0 .../course/CourseMain/CourseMain.tsx} | 6 +++--- src/constants/images.ts | 12 +++++++++++- src/pages/CourseDetailPage/index.tsx | 6 +++--- 11 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/assets/images/course_info_calendar.svg create mode 100644 src/assets/images/course_info_people.svg rename src/components/domains/{DetailPage => detail}/Header/Header.module.scss (100%) rename src/components/domains/{DetailPage => detail}/Header/Header.tsx (100%) rename src/components/domains/{DetailPage => detail}/InfoAndReviewTab/InfoAndReviewTab.module.scss (100%) rename src/components/domains/{DetailPage => detail}/InfoAndReviewTab/index.tsx (100%) rename src/components/domains/{DetailPage/CourseDetail/Header => detail/course/CourseHeader}/CourseHeader.tsx (100%) rename src/components/domains/{DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.module.scss => detail/course/CourseMain/CourseMain.module.scss} (100%) rename src/components/domains/{DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.tsx => detail/course/CourseMain/CourseMain.tsx} (92%) diff --git a/src/assets/images/course_info_calendar.svg b/src/assets/images/course_info_calendar.svg new file mode 100644 index 0000000..1b57081 --- /dev/null +++ b/src/assets/images/course_info_calendar.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/course_info_people.svg b/src/assets/images/course_info_people.svg new file mode 100644 index 0000000..d265c05 --- /dev/null +++ b/src/assets/images/course_info_people.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/domains/DetailPage/Header/Header.module.scss b/src/components/domains/detail/Header/Header.module.scss similarity index 100% rename from src/components/domains/DetailPage/Header/Header.module.scss rename to src/components/domains/detail/Header/Header.module.scss diff --git a/src/components/domains/DetailPage/Header/Header.tsx b/src/components/domains/detail/Header/Header.tsx similarity index 100% rename from src/components/domains/DetailPage/Header/Header.tsx rename to src/components/domains/detail/Header/Header.tsx diff --git a/src/components/domains/DetailPage/InfoAndReviewTab/InfoAndReviewTab.module.scss b/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss similarity index 100% rename from src/components/domains/DetailPage/InfoAndReviewTab/InfoAndReviewTab.module.scss rename to src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss diff --git a/src/components/domains/DetailPage/InfoAndReviewTab/index.tsx b/src/components/domains/detail/InfoAndReviewTab/index.tsx similarity index 100% rename from src/components/domains/DetailPage/InfoAndReviewTab/index.tsx rename to src/components/domains/detail/InfoAndReviewTab/index.tsx diff --git a/src/components/domains/DetailPage/CourseDetail/Header/CourseHeader.tsx b/src/components/domains/detail/course/CourseHeader/CourseHeader.tsx similarity index 100% rename from src/components/domains/DetailPage/CourseDetail/Header/CourseHeader.tsx rename to src/components/domains/detail/course/CourseHeader/CourseHeader.tsx diff --git a/src/components/domains/DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.module.scss b/src/components/domains/detail/course/CourseMain/CourseMain.module.scss similarity index 100% rename from src/components/domains/DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.module.scss rename to src/components/domains/detail/course/CourseMain/CourseMain.module.scss diff --git a/src/components/domains/DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.tsx b/src/components/domains/detail/course/CourseMain/CourseMain.tsx similarity index 92% rename from src/components/domains/DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.tsx rename to src/components/domains/detail/course/CourseMain/CourseMain.tsx index c645fd6..1485143 100644 --- a/src/components/domains/DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo.tsx +++ b/src/components/domains/detail/course/CourseMain/CourseMain.tsx @@ -5,11 +5,11 @@ import { defaultCourseDetail } from "@/constants/defaultValues"; import { useQuery } from "@tanstack/react-query"; import classNames from "classnames/bind"; import { useParams } from "react-router-dom"; -import styles from "./CourseDetailInfo.module.scss"; +import styles from "./CourseMain.module.scss"; const cx = classNames.bind(styles); -const CourseDetailInfo = () => { +const CourseMain = () => { const { id } = useParams<{ id: string }>(); const { data: courseDetailData } = useQuery({ @@ -43,4 +43,4 @@ const CourseDetailInfo = () => { ); }; -export default CourseDetailInfo; +export default CourseMain; diff --git a/src/constants/images.ts b/src/constants/images.ts index 3b145c3..313f03b 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -19,6 +19,8 @@ import calendarWhiteIcon from "@/assets/images/calendar_white.svg"; import ChildIcon from "@/assets/images/child.svg"; import ColumnLine from "@/assets/images/column_line.svg"; import CoupleIcon from "@/assets/images/couple.svg"; +import courseInfoCanlendar from "@/assets/images/course_info_calendar.svg"; +import courseInfoPeople from "@/assets/images/course_info_people.svg"; import courseMakerLogo from "@/assets/images/course_maker_logo.svg"; import courseMakerLogoMobile from "@/assets/images/course_maker_logo_mobile.svg"; import CultureIcon from "@/assets/images/culture.svg"; @@ -32,7 +34,6 @@ import GrayPhotoIcon from "@/assets/images/gray_photo.svg"; import GraySearchIcon from "@/assets/images/gray_search.svg"; import GrayStarIcon from "@/assets/images/gray_star.svg"; import grayTriangle from "@/assets/images/gray_triangle.svg"; -import GraySerchbarIcon from "@/assets/images/search_bar_gray.svg"; import Home_left_test from "@/assets/images/home_left_test.svg"; import Home_right_test from "@/assets/images/home_right_test.svg"; import kakaoOauthButton from "@/assets/images/kakao_oauth_button.svg"; @@ -53,6 +54,7 @@ import Rectangle_42 from "@/assets/images/Rectangle_42.svg"; import Rectangle_43 from "@/assets/images/Rectangle_43.svg"; import Rectangle_44 from "@/assets/images/Rectangle_44.svg"; import Rectangle_45 from "@/assets/images/Rectangle_45.svg"; +import GraySerchbarIcon from "@/assets/images/search_bar_gray.svg"; import TabBarHomeIcon from "@/assets/images/TabBarHomeIcon"; import TabBarMapIcon from "@/assets/images/TabBarMapIcon"; import TabBarPointerIcon from "@/assets/images/TabBarPointerIcon"; @@ -310,4 +312,12 @@ export const IMAGES = { src: GraySerchbarIcon, alt: "회색 돋보기 아이콘", }, + courseInfoCanlendar: { + src: courseInfoCanlendar, + alt: "여행기간 아이콘", + }, + courseInfoPeople: { + src: courseInfoPeople, + alt: "여행추천인원 아이콘", + }, }; diff --git a/src/pages/CourseDetailPage/index.tsx b/src/pages/CourseDetailPage/index.tsx index 4dd9b22..f9b75fb 100644 --- a/src/pages/CourseDetailPage/index.tsx +++ b/src/pages/CourseDetailPage/index.tsx @@ -1,9 +1,9 @@ -import CourseDetailInfo from "@/components/domains/DetailPage/CourseDetail/CourseDetailInfo/CourseDetailInfo"; -import CourseHeader from "@/components/domains/DetailPage/CourseDetail/Header/CourseHeader"; +import CourseHeader from "@/components/domains/detail/course/CourseHeader/CourseHeader"; +import CourseMain from "@/components/domains/detail/course/CourseMain/CourseMain"; import DetailPageLayout from "@/layout/DetailPageLayout"; const CourseDetailPage = () => { - return } main={} />; + return } main={} />; }; export default CourseDetailPage; From 52837ded2aedbea3a1656c1f68a936a6ef1140bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 14:51:58 +0900 Subject: [PATCH 003/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=97=AC=ED=96=89=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=9D=BC=EB=B6=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/detail/InfoAndReviewTab/index.tsx | 7 +-- .../detail/course/CourseMain/CourseMain.tsx | 2 +- .../TravelDurationAndGroup.module.scss | 22 +++++++++ .../TravelDurationAndGroup.tsx | 46 +++++++++++++++++++ src/layout/DetailPageLayout/index.tsx | 8 ++-- src/pages/CourseDetailPage/index.tsx | 5 +- 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss create mode 100644 src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx diff --git a/src/components/domains/detail/InfoAndReviewTab/index.tsx b/src/components/domains/detail/InfoAndReviewTab/index.tsx index 4a75cca..7e248c9 100644 --- a/src/components/domains/detail/InfoAndReviewTab/index.tsx +++ b/src/components/domains/detail/InfoAndReviewTab/index.tsx @@ -4,7 +4,7 @@ import { getDestinationReviews } from "@/api/destination"; import { getDestinationReviewsResponseDto } from "@/api/destination/type"; import { useQuery } from "@tanstack/react-query"; import classNames from "classnames/bind"; -import { useState } from "react"; +import { ReactNode, useState } from "react"; import { useParams } from "react-router-dom"; import styles from "./InfoAndReviewTab.module.scss"; @@ -13,9 +13,10 @@ const cx = classNames.bind(styles); type ReviewsResponse = getCourseReviewsResponseDto | getDestinationReviewsResponseDto; interface InfoAndReviewTabProps { type: "course" | "destination"; + info: ReactNode; } -const InfoAndReviewTab = ({ type }: InfoAndReviewTabProps) => { +const InfoAndReviewTab = ({ type, info }: InfoAndReviewTabProps) => { const { id } = useParams(); const postId = Number(id); const [tab, setTab] = useState("info"); @@ -46,7 +47,7 @@ const InfoAndReviewTab = ({ type }: InfoAndReviewTabProps) => { 리뷰({totalReviewCount}개) - {tab === "info" ?
안내
:
리뷰
} + {tab === "info" ?
{info}
:
리뷰
} ); }; diff --git a/src/components/domains/detail/course/CourseMain/CourseMain.tsx b/src/components/domains/detail/course/CourseMain/CourseMain.tsx index 1485143..0267340 100644 --- a/src/components/domains/detail/course/CourseMain/CourseMain.tsx +++ b/src/components/domains/detail/course/CourseMain/CourseMain.tsx @@ -13,7 +13,7 @@ const CourseMain = () => { const { id } = useParams<{ id: string }>(); const { data: courseDetailData } = useQuery({ - queryKey: ["courseDetailData"], + queryKey: ["courseDetailData", id], queryFn: () => getCourseDetail(Number(id)), retry: 0, }); diff --git a/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss b/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss new file mode 100644 index 0000000..7df9e50 --- /dev/null +++ b/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss @@ -0,0 +1,22 @@ +.container { + @include flexbox(around, center); + gap: 14.5rem; + + .element { + @include flexbox(center, center); + gap: 2rem; + + .icon { + width: 5.7rem; + } + + .text { + @include column-flexbox(center, start); + @include text-style(1.3, 500, $black30); + + &-stress { + @include text-style(1.6, 700, $black30); + } + } + } +} diff --git a/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx b/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx new file mode 100644 index 0000000..d2117e3 --- /dev/null +++ b/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx @@ -0,0 +1,46 @@ +import { getCourseDetail } from "@/api/course"; +import Image from "@/components/commons/Image"; +import { defaultCourseDetail } from "@/constants/defaultValues"; +import { IMAGES } from "@/constants/images"; +import { useQuery } from "@tanstack/react-query"; +import classNames from "classnames/bind"; +import { useParams } from "react-router-dom"; +import styles from "./TravelDurationAndGroup.module.scss"; + +const cx = classNames.bind(styles); + +const TravelDurationAndGroup = () => { + const { id } = useParams<{ id: string }>(); + + const { data: courseDetailData } = useQuery({ + queryKey: ["courseDetailData", id], + queryFn: () => getCourseDetail(Number(id)), + retry: 0, + }); + + const courseDetail = courseDetailData ?? defaultCourseDetail; + + return ( +
    +
  • +
    + +
    +
    +

    여행추천인원

    +

    {courseDetail.travelerCount}인

    +
    +
  • +
  • +
    + +
    +
    +

    여행기간

    +

    {courseDetail.duration}일

    +
    +
  • +
+ ); +}; +export default TravelDurationAndGroup; diff --git a/src/layout/DetailPageLayout/index.tsx b/src/layout/DetailPageLayout/index.tsx index 2077d26..d2f7416 100644 --- a/src/layout/DetailPageLayout/index.tsx +++ b/src/layout/DetailPageLayout/index.tsx @@ -1,4 +1,4 @@ -import InfoAndReviewTab from "@/components/domains/DetailPage/InfoAndReviewTab"; +import InfoAndReviewTab from "@/components/domains/detail/InfoAndReviewTab"; import classNames from "classnames/bind"; import { ReactNode } from "react"; import styles from "./DetailPageLayout.module.scss"; @@ -9,16 +9,16 @@ interface DetailPageLayoutProps { type: "course" | "destination"; header: ReactNode; main: ReactNode; - // info: ReactNode; + info: ReactNode; // review: ReactNode; } -const DetailPageLayout = ({ type, header, main }: DetailPageLayoutProps) => { +const DetailPageLayout = ({ type, header, main, info }: DetailPageLayoutProps) => { return (
{header}
{main}
- +
); }; diff --git a/src/pages/CourseDetailPage/index.tsx b/src/pages/CourseDetailPage/index.tsx index f9b75fb..e54032b 100644 --- a/src/pages/CourseDetailPage/index.tsx +++ b/src/pages/CourseDetailPage/index.tsx @@ -1,9 +1,12 @@ import CourseHeader from "@/components/domains/detail/course/CourseHeader/CourseHeader"; import CourseMain from "@/components/domains/detail/course/CourseMain/CourseMain"; +import TravelDurationAndGroup from "@/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup"; import DetailPageLayout from "@/layout/DetailPageLayout"; const CourseDetailPage = () => { - return } main={} />; + return ( + } main={} info={} /> + ); }; export default CourseDetailPage; From 2358e6f2b9ba4558dfa3d8cef0addbd6a3451426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 16:14:19 +0900 Subject: [PATCH 004/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20destinationCard=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/destinationPin.svg | 3 ++ .../CardContent/CardContent.module.scss | 2 +- .../DestinationCard.module.scss | 37 +++++++++++++++++++ .../TravelCourseOnMap/DestinationCard.tsx | 30 +++++++++++++++ .../TravelCourseOnMap.module.scss | 0 .../TravelCourseOnMap/TravelCourseOnMap.tsx | 30 +++++++++++++++ src/constants/images.ts | 5 +++ src/pages/CourseDetailPage/index.tsx | 4 +- 8 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/assets/images/destinationPin.svg create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.module.scss create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx diff --git a/src/assets/images/destinationPin.svg b/src/assets/images/destinationPin.svg new file mode 100644 index 0000000..c64f2c3 --- /dev/null +++ b/src/assets/images/destinationPin.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/commons/CardContent/CardContent.module.scss b/src/components/commons/CardContent/CardContent.module.scss index eb425f3..fd8091f 100644 --- a/src/components/commons/CardContent/CardContent.module.scss +++ b/src/components/commons/CardContent/CardContent.module.scss @@ -4,7 +4,7 @@ border-radius: 1rem; width: 100%; height: 12rem; - box-shadow: 0rem 0.2rem 0.4rem rgba(0, 0, 0, 0.1); + box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.08); padding: 1.2rem; background-color: $white; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss new file mode 100644 index 0000000..65e7ae6 --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss @@ -0,0 +1,37 @@ +.container { + background-color: $white; + padding: 1.2rem 2rem; + border-radius: 1rem; + box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.08); + + .content { + @include flexbox(stretch, center); + gap: 2rem; + + .icon { + width: 3.5rem; + position: relative; + flex-shrink: 0; + + &-num { + @include text-style(1.2, 700, $black30); + @include pos-center-x(); + top: 1rem; + } + } + + .text { + text-overflow: ellipsis; + word-break: break-all; + + &-title { + @include text-style(1.4, 700, $black30); + padding-bottom: 0.2rem; + } + + &-address { + @include text-style(1.2, 500, $black30); + } + } + } +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx new file mode 100644 index 0000000..ec7499b --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx @@ -0,0 +1,30 @@ +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import classNames from "classnames/bind"; +import styles from "./DestinationCard.module.scss"; + +const cx = classNames.bind(styles); + +interface DestinationCardProps { + number: number; + title: string; + address: string; +} + +const DestinationCard = ({ number, title, address }: DestinationCardProps) => { + return ( +
+
+
+ + {number} +
+
+

{title}

+

{address}

+
+
+
+ ); +}; +export default DestinationCard; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx new file mode 100644 index 0000000..93a88c5 --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx @@ -0,0 +1,30 @@ +// import { getCourseDetail } from "@/api/course"; +// import { defaultCourseDetail } from "@/constants/defaultValues"; +// import { useQuery } from "@tanstack/react-query"; +// import { useParams } from "react-router-dom"; +import DestinationCard from "./DestinationCard"; + +const TravelCourseOnMap = () => { + // const { id } = useParams<{ id: string }>(); + + // const { data: courseDetailData } = useQuery({ + // queryKey: ["courseDetailData", id], + // queryFn: () => getCourseDetail(Number(id)), + // retry: 0, + // }); + + // const courseDetail = courseDetailData ?? defaultCourseDetail; + + return ( + <> +

여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

+ + + + ); +}; +export default TravelCourseOnMap; diff --git a/src/constants/images.ts b/src/constants/images.ts index 313f03b..d442e25 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -24,6 +24,7 @@ import courseInfoPeople from "@/assets/images/course_info_people.svg"; import courseMakerLogo from "@/assets/images/course_maker_logo.svg"; import courseMakerLogoMobile from "@/assets/images/course_maker_logo_mobile.svg"; import CultureIcon from "@/assets/images/culture.svg"; +import destinationPin from "@/assets/images/destinationPin.svg"; import PetIcon from "@/assets/images/dog.svg"; import Down_arrow from "@/assets/images/down_arrow.svg"; import GrayBookmarkIcon from "@/assets/images/gray_bookmark.svg"; @@ -320,4 +321,8 @@ export const IMAGES = { src: courseInfoPeople, alt: "여행추천인원 아이콘", }, + destinationPin: { + src: destinationPin, + alt: "여행지 핀", + }, }; diff --git a/src/pages/CourseDetailPage/index.tsx b/src/pages/CourseDetailPage/index.tsx index e54032b..0394d9f 100644 --- a/src/pages/CourseDetailPage/index.tsx +++ b/src/pages/CourseDetailPage/index.tsx @@ -1,11 +1,11 @@ import CourseHeader from "@/components/domains/detail/course/CourseHeader/CourseHeader"; import CourseMain from "@/components/domains/detail/course/CourseMain/CourseMain"; -import TravelDurationAndGroup from "@/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup"; +import TravelCourseOnMap from "@/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap"; import DetailPageLayout from "@/layout/DetailPageLayout"; const CourseDetailPage = () => { return ( - } main={} info={} /> + } main={} info={} /> ); }; From 3ca0f8b21b31197739a7b77811f1a9e5b7449571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 20:29:56 +0900 Subject: [PATCH 005/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20TransitTimeChip=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/gray_bus.svg | 3 ++ src/assets/images/gray_car.svg | 3 ++ src/assets/images/gray_transit_triangle.svg | 3 ++ .../TransitTimeChip.module.scss | 27 ++++++++++++++++++ .../TravelCourseOnMap/TransitTimeChip.tsx | 28 +++++++++++++++++++ .../{TravelCourseOnMap.tsx => index.tsx} | 2 ++ src/constants/images.ts | 15 ++++++++++ src/pages/CourseDetailPage/index.tsx | 2 +- 8 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/assets/images/gray_bus.svg create mode 100644 src/assets/images/gray_car.svg create mode 100644 src/assets/images/gray_transit_triangle.svg create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx rename src/components/domains/detail/course/TravelCourseOnMap/{TravelCourseOnMap.tsx => index.tsx} (91%) diff --git a/src/assets/images/gray_bus.svg b/src/assets/images/gray_bus.svg new file mode 100644 index 0000000..ca3757c --- /dev/null +++ b/src/assets/images/gray_bus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/gray_car.svg b/src/assets/images/gray_car.svg new file mode 100644 index 0000000..8be1a1c --- /dev/null +++ b/src/assets/images/gray_car.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/gray_transit_triangle.svg b/src/assets/images/gray_transit_triangle.svg new file mode 100644 index 0000000..d684dee --- /dev/null +++ b/src/assets/images/gray_transit_triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss new file mode 100644 index 0000000..43e0383 --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss @@ -0,0 +1,27 @@ +.container { + @include inline-flexbox(); + background-color: $primary10; + border-radius: 1rem; + padding: 0.8rem 1rem; + + .content { + @include flexbox(); + gap: 0.5rem; + + .icon { + @include flexbox(); + + &.transit { + width: 1.5rem; + } + + &.triangle { + width: 0.6rem; + } + } + + .time { + @include text-style(1.2, 400, $black10); + } + } +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx new file mode 100644 index 0000000..97add6a --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx @@ -0,0 +1,28 @@ +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import classNames from "classnames/bind"; +import styles from "./TransitTimeChip.module.scss"; + +const cx = classNames.bind(styles); + +interface TransitTimeChipProps { + transit: "private" | "public"; + time: string; +} + +const TransitTimeChip = ({ transit, time }: TransitTimeChipProps) => { + return ( +
+
+
+ {transit === "private" ? : } +
+

{time}

+
+ +
+
+
+ ); +}; +export default TransitTimeChip; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx similarity index 91% rename from src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx rename to src/components/domains/detail/course/TravelCourseOnMap/index.tsx index 93a88c5..8433298 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx @@ -3,6 +3,7 @@ // import { useQuery } from "@tanstack/react-query"; // import { useParams } from "react-router-dom"; import DestinationCard from "./DestinationCard"; +import TransitTimeChip from "./TransitTimeChip"; const TravelCourseOnMap = () => { // const { id } = useParams<{ id: string }>(); @@ -19,6 +20,7 @@ const TravelCourseOnMap = () => { <>

여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

+ { From 4d61b1ef99258498c3fc1ba9590978c5cb227a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 22:35:30 +0900 Subject: [PATCH 006/131] =?UTF-8?q?Feat:=20=EC=BD=94=EC=8A=A4=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=EB=B3=84=20=EB=B2=84=ED=8A=BC=EC=9D=84=20?= =?UTF-8?q?=EB=88=84=EB=A5=B4=EB=A9=B4=20=ED=95=B4=EB=8B=B9=20=EC=97=AC?= =?UTF-8?q?=ED=96=89=EC=A7=80=EB=B0=8F=20=EC=A7=80=EB=8F=84=EC=97=90=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=EA=B0=80=20=EB=82=98=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TravelCourseOnMap/DateTab.module.scss | 16 +++++++ .../course/TravelCourseOnMap/DateTab.tsx | 26 ++++++++++ .../DestinationCard.module.scss | 6 +-- .../DestinationList.module.scss | 0 .../TravelCourseOnMap/DestinationList.tsx | 33 +++++++++++++ .../TransitTimeChip.module.scss | 1 + .../course/TravelCourseOnMap/TravelMap.tsx | 29 +++++++++++ .../detail/course/TravelCourseOnMap/index.tsx | 48 +++++++++++-------- 8 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DateTab.module.scss create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DestinationList.module.scss create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DateTab.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/DateTab.module.scss new file mode 100644 index 0000000..b58b8b2 --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/DateTab.module.scss @@ -0,0 +1,16 @@ +.container { + @include inline-flexbox(stretch, center); + gap: 3rem; + background-color: $gray10; + border-radius: 0.5rem; + padding: 1rem 2.7rem; + + .btn { + @include text-style(1.6, 500, $gray30); + cursor: pointer; + + &.isSelected { + @include text-style(1.6, 700, $black30); + } + } +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx new file mode 100644 index 0000000..9dec4ce --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx @@ -0,0 +1,26 @@ +import classNames from "classnames/bind"; +import styles from "./DateTab.module.scss"; + +const cx = classNames.bind(styles); + +interface DateTabProps { + days: number[]; + selectedDate: number; + setSelectedDate: React.Dispatch>; +} + +const DateTab = ({ days, selectedDate, setSelectedDate }: DateTabProps) => { + return ( +
+ {days.map((day) => ( + + ))} +
+ ); +}; +export default DateTab; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss index 65e7ae6..998d3e7 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss @@ -3,6 +3,7 @@ padding: 1.2rem 2rem; border-radius: 1rem; box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.08); + cursor: pointer; .content { @include flexbox(stretch, center); @@ -21,12 +22,9 @@ } .text { - text-overflow: ellipsis; - word-break: break-all; - &-title { @include text-style(1.4, 700, $black30); - padding-bottom: 0.2rem; + padding-bottom: 0.5rem; } &-address { diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.module.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx new file mode 100644 index 0000000..16d453d --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx @@ -0,0 +1,33 @@ +import { CourseDestination } from "@/api/course/register"; +import DateTab from "./DateTab"; +import DestinationCard from "./DestinationCard"; + +interface DestinationListProps { + duration: number; + courseDestinations: CourseDestination[]; + selectedDestinations: CourseDestination[]; + selectedDate: number; + setSelectedDate: React.Dispatch>; +} + +const DestinationList = ({ duration, selectedDestinations, selectedDate, setSelectedDate }: DestinationListProps) => { + const days = Array.from({ length: duration }, (_, i) => i + 1); + + return ( + <> + +

여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

+
+ {selectedDestinations.map((destination) => ( + + ))} +
+ + ); +}; +export default DestinationList; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss index 43e0383..d0a8517 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss +++ b/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss @@ -3,6 +3,7 @@ background-color: $primary10; border-radius: 1rem; padding: 0.8rem 1rem; + cursor: pointer; .content { @include flexbox(); diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx new file mode 100644 index 0000000..af5f825 --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx @@ -0,0 +1,29 @@ +import { CourseDestination } from "@/api/course/register"; +import { Map, MapMarker, Polyline } from "react-kakao-maps-sdk"; + +interface TravelMapProps { + destinations: CourseDestination[]; +} + +const TravelMap = ({ destinations }: TravelMapProps) => { + const positions = destinations.map((destination) => ({ + lat: destination.destination.location.latitude, + lng: destination.destination.location.longitude, + })); + + return ( + + {/* 여행지 마커 표시 */} + {positions.map((position, index) => ( + +
{destinations[index].destination.name}
+
+ ))} + + {/* 여행지 경로를 선으로 연결 */} + +
+ ); +}; + +export default TravelMap; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx index 8433298..37f6482 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx @@ -1,31 +1,39 @@ -// import { getCourseDetail } from "@/api/course"; -// import { defaultCourseDetail } from "@/constants/defaultValues"; -// import { useQuery } from "@tanstack/react-query"; -// import { useParams } from "react-router-dom"; -import DestinationCard from "./DestinationCard"; -import TransitTimeChip from "./TransitTimeChip"; +import { getCourseDetail } from "@/api/course"; +import { defaultCourseDetail } from "@/constants/defaultValues"; +import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; +import { useParams } from "react-router-dom"; + +import DestinationList from "./DestinationList"; +import TravelMap from "./TravelMap"; const TravelCourseOnMap = () => { - // const { id } = useParams<{ id: string }>(); + const [selectedDate, setSelectedDate] = useState(1); + + const { id } = useParams<{ id: string }>(); - // const { data: courseDetailData } = useQuery({ - // queryKey: ["courseDetailData", id], - // queryFn: () => getCourseDetail(Number(id)), - // retry: 0, - // }); + const { data: courseDetailData } = useQuery({ + queryKey: ["courseDetailData", id], + queryFn: () => getCourseDetail(Number(id)), + retry: 0, + }); - // const courseDetail = courseDetailData ?? defaultCourseDetail; + const courseDetail = courseDetailData ?? defaultCourseDetail; + + const selectedDestinations = courseDetail.courseDestinations.filter( + (destination) => destination.date === selectedDate, + ); return ( <> -

여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

- - - + ); }; From 204fcd767b5ae84c6ff3146a96830300f11558ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 23:10:17 +0900 Subject: [PATCH 007/131] =?UTF-8?q?Feat:=20=EB=82=A0=EC=A7=9C=EB=A5=BC=20?= =?UTF-8?q?=EB=88=84=EB=A5=B4=EB=A9=B4=20=ED=95=B4=EB=8B=B9=20=EB=82=A0?= =?UTF-8?q?=EC=9D=98=20=EB=AA=A8=EB=93=A0=20=EA=B2=BD=EB=A1=9C=EA=B0=80=20?= =?UTF-8?q?=ED=95=9C=EB=88=88=EC=97=90=20=EB=B3=B4=EC=9D=B4=EA=B2=8C=20?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TravelCourseOnMap/DestinationList.tsx | 1 - .../course/TravelCourseOnMap/TravelMap.tsx | 32 +++++++++++++++---- .../detail/course/TravelCourseOnMap/index.tsx | 3 +- src/constants/defaultValues.ts | 26 ++++++++++++++- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx index 16d453d..11bd723 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx @@ -4,7 +4,6 @@ import DestinationCard from "./DestinationCard"; interface DestinationListProps { duration: number; - courseDestinations: CourseDestination[]; selectedDestinations: CourseDestination[]; selectedDate: number; setSelectedDate: React.Dispatch>; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx index af5f825..563ee39 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx @@ -1,6 +1,6 @@ import { CourseDestination } from "@/api/course/register"; -import { Map, MapMarker, Polyline } from "react-kakao-maps-sdk"; - +import { useEffect } from "react"; +import { Map, MapMarker, Polyline, useMap } from "react-kakao-maps-sdk"; interface TravelMapProps { destinations: CourseDestination[]; } @@ -13,16 +13,36 @@ const TravelMap = ({ destinations }: TravelMapProps) => { return ( - {/* 여행지 마커 표시 */} + + + ); +}; + +interface MarkersAndPolylineProps { + positions: { lat: number; lng: number }[]; +} + +const MarkersAndPolyline = ({ positions }: MarkersAndPolylineProps) => { + const map = useMap(); + + useEffect(() => { + if (map && positions.length > 0) { + const bounds = new kakao.maps.LatLngBounds(); + positions.forEach((pos) => bounds.extend(new kakao.maps.LatLng(pos.lat, pos.lng))); + map.setBounds(bounds); + } + }, [map, positions]); + + return ( + <> {positions.map((position, index) => ( -
{destinations[index].destination.name}
+
{`Marker ${index + 1}`}
))} - {/* 여행지 경로를 선으로 연결 */} - + ); }; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx index 37f6482..e05d42e 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx @@ -28,12 +28,11 @@ const TravelCourseOnMap = () => { <> - + {selectedDestinations.length > 0 && } ); }; diff --git a/src/constants/defaultValues.ts b/src/constants/defaultValues.ts index 8c18ec7..9292fe7 100644 --- a/src/constants/defaultValues.ts +++ b/src/constants/defaultValues.ts @@ -7,7 +7,31 @@ export const defaultCourseDetail = { travelerCount: 0, travelType: 0, pictureLink: "", - courseDestinations: [], + courseDestinations: [ + { + visitOrder: 0, + date: 0, + destination: { + id: 0, + nickname: "", + name: "", + views: 0, + tags: [], + location: { address: "", longitude: 0, latitude: 0 }, + pictureLink: "", + content: "", + averageRating: 0, + isMyDestination: false, + disabled: false, + isApiData: false, + wishCount: 0, + reviewCount: 0, + likeCount: 0, + isMyWishDestination: false, + isMyLikeDestination: false, + }, + }, + ], tags: [], member: { nickname: "", From f6b53f157378f6d47a4facbbc6fcf7f120d1b7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sun, 8 Sep 2024 23:59:55 +0900 Subject: [PATCH 008/131] =?UTF-8?q?Feat:=20=EC=97=AC=ED=96=89=EC=A7=80=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20=ED=95=B4=EB=8B=B9=20=EC=97=AC?= =?UTF-8?q?=ED=96=89=EC=A7=80=EA=B0=80=20=EC=A7=80=EB=8F=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=99=95=EB=8C=80=EB=90=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=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/destinationPin_stress.svg | 3 +++ .../course/TravelCourseOnMap/DateTab.tsx | 9 +++---- .../TravelCourseOnMap/DestinationCard.tsx | 8 ++++--- .../TravelCourseOnMap/DestinationList.tsx | 24 ++++++++++++++++--- .../TravelCourseOnMap/TransitChangeToggle.tsx | 8 +++++++ .../course/TravelCourseOnMap/TravelMap.tsx | 18 +++++++++----- .../detail/course/TravelCourseOnMap/index.tsx | 17 ++++++++++--- src/constants/images.ts | 5 ++++ src/type/type.d.ts | 10 ++++++++ 9 files changed, 81 insertions(+), 21 deletions(-) create mode 100644 src/assets/images/destinationPin_stress.svg create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx diff --git a/src/assets/images/destinationPin_stress.svg b/src/assets/images/destinationPin_stress.svg new file mode 100644 index 0000000..5f4fed9 --- /dev/null +++ b/src/assets/images/destinationPin_stress.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx index 9dec4ce..8def591 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx @@ -6,17 +6,14 @@ const cx = classNames.bind(styles); interface DateTabProps { days: number[]; selectedDate: number; - setSelectedDate: React.Dispatch>; + onClick: (day: number) => void; } -const DateTab = ({ days, selectedDate, setSelectedDate }: DateTabProps) => { +const DateTab = ({ days, selectedDate, onClick }: DateTabProps) => { return (
{days.map((day) => ( - ))} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx index ec7499b..cb0bc5f 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx @@ -9,14 +9,16 @@ interface DestinationCardProps { number: number; title: string; address: string; + isSelected: boolean; + onClick: () => void; } -const DestinationCard = ({ number, title, address }: DestinationCardProps) => { +const DestinationCard = ({ number, title, address, isSelected, onClick }: DestinationCardProps) => { return ( -
+
- + {isSelected ? : } {number}
diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx index 11bd723..c2502fe 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx @@ -1,4 +1,5 @@ import { CourseDestination } from "@/api/course/register"; +import { LocationWithId } from "@/type/type"; import DateTab from "./DateTab"; import DestinationCard from "./DestinationCard"; @@ -6,15 +7,24 @@ interface DestinationListProps { duration: number; selectedDestinations: CourseDestination[]; selectedDate: number; - setSelectedDate: React.Dispatch>; + onClick: (day: number) => void; + selectedLocation: LocationWithId | null; + setSelectedLocation: React.Dispatch>; } -const DestinationList = ({ duration, selectedDestinations, selectedDate, setSelectedDate }: DestinationListProps) => { +const DestinationList = ({ + duration, + selectedDestinations, + selectedDate, + onClick, + selectedLocation, + setSelectedLocation, +}: DestinationListProps) => { const days = Array.from({ length: duration }, (_, i) => i + 1); return ( <> - +

여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

{selectedDestinations.map((destination) => ( @@ -23,6 +33,14 @@ const DestinationList = ({ duration, selectedDestinations, selectedDate, setSele number={destination.visitOrder} title={destination.destination.name} address={destination.destination.location.address} + isSelected={selectedLocation?.id === destination.destination.id} + onClick={() => { + setSelectedLocation({ + id: destination.destination.id, + lat: destination.destination.location.latitude, + lng: destination.destination.location.longitude, + }); + }} /> ))}
diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx new file mode 100644 index 0000000..5e28737 --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx @@ -0,0 +1,8 @@ +const TransitChangeToggle = () => { + return ( + <> +

TransitChangeToggle Component

+ + ); +}; +export default TransitChangeToggle; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx index 563ee39..41f91c0 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx @@ -1,11 +1,13 @@ import { CourseDestination } from "@/api/course/register"; +import { Location, LocationWithId } from "@/type/type"; import { useEffect } from "react"; import { Map, MapMarker, Polyline, useMap } from "react-kakao-maps-sdk"; interface TravelMapProps { destinations: CourseDestination[]; + selectedLocation: LocationWithId | null; } -const TravelMap = ({ destinations }: TravelMapProps) => { +const TravelMap = ({ destinations, selectedLocation }: TravelMapProps) => { const positions = destinations.map((destination) => ({ lat: destination.destination.location.latitude, lng: destination.destination.location.longitude, @@ -13,25 +15,29 @@ const TravelMap = ({ destinations }: TravelMapProps) => { return ( - + ); }; interface MarkersAndPolylineProps { - positions: { lat: number; lng: number }[]; + positions: Location[]; + selectedLocation: LocationWithId | null; } -const MarkersAndPolyline = ({ positions }: MarkersAndPolylineProps) => { +const MarkersAndPolyline = ({ positions, selectedLocation }: MarkersAndPolylineProps) => { const map = useMap(); useEffect(() => { - if (map && positions.length > 0) { + if (map && selectedLocation) { + map.setCenter(new kakao.maps.LatLng(selectedLocation.lat, selectedLocation.lng)); + map.setLevel(1); + } else if (map && positions.length > 0) { const bounds = new kakao.maps.LatLngBounds(); positions.forEach((pos) => bounds.extend(new kakao.maps.LatLng(pos.lat, pos.lng))); map.setBounds(bounds); } - }, [map, positions]); + }, [map, selectedLocation, positions]); return ( <> diff --git a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx index e05d42e..6cdbb81 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx @@ -1,5 +1,6 @@ import { getCourseDetail } from "@/api/course"; import { defaultCourseDetail } from "@/constants/defaultValues"; +import { LocationWithId } from "@/type/type"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; import { useParams } from "react-router-dom"; @@ -8,7 +9,13 @@ import DestinationList from "./DestinationList"; import TravelMap from "./TravelMap"; const TravelCourseOnMap = () => { - const [selectedDate, setSelectedDate] = useState(1); + const [selectedDate, setSelectedDate] = useState(1); + const [selectedLocation, setSelectedLocation] = useState(null); + + const handleDestinationClick = (day: number) => { + setSelectedDate(day); + setSelectedLocation(null); + }; const { id } = useParams<{ id: string }>(); @@ -30,9 +37,13 @@ const TravelCourseOnMap = () => { duration={courseDetail.duration} selectedDestinations={selectedDestinations} selectedDate={selectedDate} - setSelectedDate={setSelectedDate} + onClick={handleDestinationClick} + selectedLocation={selectedLocation} + setSelectedLocation={setSelectedLocation} /> - {selectedDestinations.length > 0 && } + {selectedDestinations.length > 0 && ( + + )} ); }; diff --git a/src/constants/images.ts b/src/constants/images.ts index f2935b8..09f73f9 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -25,6 +25,7 @@ import courseMakerLogo from "@/assets/images/course_maker_logo.svg"; import courseMakerLogoMobile from "@/assets/images/course_maker_logo_mobile.svg"; import CultureIcon from "@/assets/images/culture.svg"; import destinationPin from "@/assets/images/destinationPin.svg"; +import destinationPinStress from "@/assets/images/destinationPin_stress.svg"; import PetIcon from "@/assets/images/dog.svg"; import Down_arrow from "@/assets/images/down_arrow.svg"; import GrayBookmarkIcon from "@/assets/images/gray_bookmark.svg"; @@ -340,4 +341,8 @@ export const IMAGES = { src: grayTransitTriangle, alt: "삼각형", }, + destinationPinStress: { + src: destinationPinStress, + alt: "여행지 핀 강조", + }, }; diff --git a/src/type/type.d.ts b/src/type/type.d.ts index ffccc08..cd1b77d 100644 --- a/src/type/type.d.ts +++ b/src/type/type.d.ts @@ -7,3 +7,13 @@ export interface Status { status: string; message: string; } + +export interface Location { + lat: number; + lng: number; +} +export interface LocationWithId { + id: number; + lat: number; + lng: number; +} From 740e77c74f5898b5ffaf7f6f82b9ed452128557c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Mon, 9 Sep 2024 23:17:03 +0900 Subject: [PATCH 009/131] =?UTF-8?q?Feat:=20=EA=B5=90=ED=86=B5=20=EC=88=98?= =?UTF-8?q?=EB=8B=A8=20=EC=84=A0=ED=83=9D=20=ED=86=A0=EA=B8=80=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=A0=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/deep_gray_bus.svg | 3 ++ src/assets/images/deep_gray_car.svg | 3 ++ src/assets/images/white_bus.svg | 3 ++ src/assets/images/white_car.svg | 3 ++ src/assets/styles/mixins/_flexbox.scss | 7 ++++ .../TransitChangeToggle.module.scss | 31 ++++++++++++++ .../TravelCourseOnMap/TransitChangeToggle.tsx | 41 +++++++++++++++++-- .../course/TravelCourseOnMap/TravelMap.tsx | 14 +++++-- .../detail/course/TravelCourseOnMap/index.tsx | 13 +++++- src/constants/images.ts | 20 +++++++++ 10 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 src/assets/images/deep_gray_bus.svg create mode 100644 src/assets/images/deep_gray_car.svg create mode 100644 src/assets/images/white_bus.svg create mode 100644 src/assets/images/white_car.svg create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.module.scss diff --git a/src/assets/images/deep_gray_bus.svg b/src/assets/images/deep_gray_bus.svg new file mode 100644 index 0000000..e99857f --- /dev/null +++ b/src/assets/images/deep_gray_bus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/deep_gray_car.svg b/src/assets/images/deep_gray_car.svg new file mode 100644 index 0000000..b489f13 --- /dev/null +++ b/src/assets/images/deep_gray_car.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/white_bus.svg b/src/assets/images/white_bus.svg new file mode 100644 index 0000000..2a10409 --- /dev/null +++ b/src/assets/images/white_bus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/white_car.svg b/src/assets/images/white_car.svg new file mode 100644 index 0000000..af1c6fc --- /dev/null +++ b/src/assets/images/white_car.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/styles/mixins/_flexbox.scss b/src/assets/styles/mixins/_flexbox.scss index 6745368..fff3f6e 100644 --- a/src/assets/styles/mixins/_flexbox.scss +++ b/src/assets/styles/mixins/_flexbox.scss @@ -29,3 +29,10 @@ $flex-map: ( align-items: flex-value($ai); justify-content: flex-value($jc); } + +@mixin inline-column-flexbox($jc: center, $ai: center) { + display: inline-flex; + flex-direction: column; + align-items: flex-value($ai); + justify-content: flex-value($jc); +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.module.scss new file mode 100644 index 0000000..b078e1f --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.module.scss @@ -0,0 +1,31 @@ +.btns { + @include inline-column-flexbox(); + gap: 1rem; + background-color: $gray30; + border-radius: 0.4rem; + padding: 0.5rem; + + .btn { + @include column-flexbox(); + border-radius: 0.4rem; + padding: 1rem 0; + width: 5.8rem; + cursor: pointer; + + &.isSelected { + background-color: $primary10; + + .text { + color: $black10; + } + } + + .icon { + width: 1.6rem; + } + + .text { + @include text-style(1.6, 700, $gray10); + } + } +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx index 5e28737..df6e018 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx @@ -1,8 +1,41 @@ -const TransitChangeToggle = () => { +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import classNames from "classnames/bind"; +import styles from "./TransitChangeToggle.module.scss"; + +const cx = classNames.bind(styles); + +interface TransitChangeToggleProps { + selectedTransit: "car" | "bus"; + onClick: () => void; +} + +const TransitChangeToggle = ({ selectedTransit, onClick }: TransitChangeToggleProps) => { return ( - <> -

TransitChangeToggle Component

- +
+
+
+
+ {selectedTransit === "car" ? ( + + ) : ( + + )} +
+ 자동차 +
+
+
+ {selectedTransit === "bus" ? ( + + ) : ( + + )} +
+ 대중교통 +
+
+
); }; export default TransitChangeToggle; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx index 41f91c0..762206b 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx @@ -2,21 +2,27 @@ import { CourseDestination } from "@/api/course/register"; import { Location, LocationWithId } from "@/type/type"; import { useEffect } from "react"; import { Map, MapMarker, Polyline, useMap } from "react-kakao-maps-sdk"; +import TransitChangeToggle from "./TransitChangeToggle"; interface TravelMapProps { destinations: CourseDestination[]; selectedLocation: LocationWithId | null; + selectedTransit: "car" | "bus"; + onClick: () => void; } -const TravelMap = ({ destinations, selectedLocation }: TravelMapProps) => { +const TravelMap = ({ destinations, selectedLocation, selectedTransit, onClick }: TravelMapProps) => { const positions = destinations.map((destination) => ({ lat: destination.destination.location.latitude, lng: destination.destination.location.longitude, })); return ( - - - + <> + + + + + ); }; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx index 6cdbb81..6dbc27e 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx @@ -11,12 +11,18 @@ import TravelMap from "./TravelMap"; const TravelCourseOnMap = () => { const [selectedDate, setSelectedDate] = useState(1); const [selectedLocation, setSelectedLocation] = useState(null); + const [selectedTransit, setSelectedTransit] = useState<"car" | "bus">("car"); const handleDestinationClick = (day: number) => { setSelectedDate(day); setSelectedLocation(null); }; + const handleTransitClick = () => { + if (selectedTransit === "car") setSelectedTransit("bus"); + else if (selectedTransit === "bus") setSelectedTransit("car"); + }; + const { id } = useParams<{ id: string }>(); const { data: courseDetailData } = useQuery({ @@ -42,7 +48,12 @@ const TravelCourseOnMap = () => { setSelectedLocation={setSelectedLocation} /> {selectedDestinations.length > 0 && ( - + )} ); diff --git a/src/constants/images.ts b/src/constants/images.ts index 09f73f9..244de37 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -24,6 +24,8 @@ import courseInfoPeople from "@/assets/images/course_info_people.svg"; import courseMakerLogo from "@/assets/images/course_maker_logo.svg"; import courseMakerLogoMobile from "@/assets/images/course_maker_logo_mobile.svg"; import CultureIcon from "@/assets/images/culture.svg"; +import deepGrayBus from "@/assets/images/deep_gray_bus.svg"; +import deepGrayCar from "@/assets/images/deep_gray_car.svg"; import destinationPin from "@/assets/images/destinationPin.svg"; import destinationPinStress from "@/assets/images/destinationPin_stress.svg"; import PetIcon from "@/assets/images/dog.svg"; @@ -66,6 +68,8 @@ import TabBarPointerIcon from "@/assets/images/TabBarPointerIcon"; import TabBarSearchIcon from "@/assets/images/TabBarSearchIcon"; import termMore from "@/assets/images/term_more.svg"; import testImage from "@/assets/images/test_img.png"; +import whiteBus from "@/assets/images/white_bus.svg"; +import whiteCar from "@/assets/images/white_car.svg"; import WhiteStarIcon from "@/assets/images/white_star.svg"; export const IMAGES = { @@ -345,4 +349,20 @@ export const IMAGES = { src: destinationPinStress, alt: "여행지 핀 강조", }, + deepGrayCar: { + src: deepGrayCar, + alt: "자가용 아이콘 선택", + }, + deepGrayBus: { + src: deepGrayBus, + alt: "대중교통 아이콘 선택", + }, + whiteCar: { + src: whiteCar, + alt: "자가용 아이콘 미선택", + }, + whiteBus: { + src: whiteBus, + alt: "대중교통 아이콘 미선택", + }, }; From baa7778017c5ec923e1e5572cd08bc3b916cc609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Tue, 10 Sep 2024 21:10:59 +0900 Subject: [PATCH 010/131] =?UTF-8?q?Feat:=20=EC=A7=80=EB=8F=84=20=EC=9C=84?= =?UTF-8?q?=20=EB=A7=88=EC=BB=A4=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/images/pin_on_map.png | Bin 0 -> 1799 bytes src/assets/styles/variables/_z-index.scss | 4 ++++ .../MarkersAndPolyline.module.scss | 21 +++++++++++++++++ .../course/TravelCourseOnMap/TravelMap.tsx | 22 ++++++++++++++---- src/constants/images.ts | 5 ++++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/assets/images/pin_on_map.png create mode 100644 src/components/domains/detail/course/TravelCourseOnMap/MarkersAndPolyline.module.scss diff --git a/src/assets/images/pin_on_map.png b/src/assets/images/pin_on_map.png new file mode 100644 index 0000000000000000000000000000000000000000..330ecfe6aa772131c157c55c9a91939aeddb3cf4 GIT binary patch literal 1799 zcmV+i2l)7jP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H129!xe zK~#90?VD?C9Mu)ae`j{K2_hgKN@zh80pcZ|*>!L!FRKNLka$R7-~)(PR4AfNS|VET zx(vyhIy?$hDuDrsM5_NBmx3WqQtu!3QdVnLY%g=;Fi!hDa7{f96mUy;wXFP z%mdSb{$R!Bp14kH#eu?l7p}b5s^Qr_ZhnkgRQv}dEr%uMt%tZAj z;E)FCwxG->Tf1L^_Xko)ZyZt;K!c8nRJZ;H^mbrUn%uw$!lR7vTbWzloEB+n0%-Tt zGf;0naCBOnb?_Gf{6T&7VBLVIZ$*AwOGF_yHRt&;XCo{5*tP z8%-n`WC4SEqdZm~P0EmX1t252cI^8}%R-pPkVYq||tJg|ExU*G*4xiD@r-644!nMF@M&c_ucKsmmb9*j3WtC)* z7TT@=U1%J?4E64ip8n;rVsuZNhK9Ysk4ytZu9G>XXX3p#8 zbm=L*6gS4WcwKFmBl@@@-d6nD$#KsL@t*-T?~jqm2p@y|pwY<#jB#Z0z@H;rCsFku ziOnC!S-Ym5gLq3L+&-i_a<24#_+{K=oU!jb{a`Y+w}F!*Tz?|P@|#xPcq>3;c7(?u ze(jR9Cl4UMTlEFLcLDHeglkle<6ss*@5<*u&ULtiXxQ0|^ZE`*(Ek~D*TeW7WEZq+ zcfQ@qXJn3`_F>lW%0mcp*dbz`87%~NG(1xMm9AKob zUAWYGII^vPV?H?*wihsVj|Ea(wZX)d0;Fg4M$i`=E|WR_lQs*NO#qLx-5GiFO+4vh zq0<_2riH}#43JYD#w~mv#yaJtg3UvVfv@V?MNLO(qX6M?^=(>dP`%h~mlYNon*iDD z+d#FhU5I*9Ocfg7A>yh=4>L01Jqwpf00~xX0{-N1IdQB|+9FhQmjTDrwX41*_buCE zp|aNlM4xurRp)BZG11JI5E`5~3FP-qCy%GBe0BjcvJm*9u3b2a>d?bxzNDy5ybt#^ z`@hHxzhb2`3m`%HE8xKhubZ_yALFtl9<;mdHsEIwZgVAA<2oUZL%gD>zil4yPp6O4 z*9;M~&~_85Q7dc%{(gONc;jD;bmcpdh+@Q8drL2a+}-G_vIZSKwV@tV?uc{_+u<1=|HYDH+!;WM)Jd_-p;Twu(xjKAMm61)@phB-NEC7>fGh?MYlJ6x0t_IWC&lvm zq!bOc$VjpB5x#aHtZjrR8ML2lcxEG$hZK5*B&e(-uGI4k3DX?19R!o7pA41I=Fn0GY}YAf*^1DrCzo zxp8iZ01^-)%*6H)grdyYwUM$}Xw3=$WKL-ru*l&0tn`#!^~Y4vG64WkZx*gQefS!m zyIPeFv{(S?>Hj;z19csgUJ1&7Yhg+)UI74GzthX^uiE~>@MuKMF@3aL0C{M|*T`;` zx#}!=-~-c*ZmRjTIv{NS)}WAI3OkywbUDS!S*`k1ZfCUqUjPtLL}oD7@fjkSnR{aL pdmLKG6}6Bn?&&=CVlTeM_y)P30a~M`rzZda002ovPDHLkV1i&2M|=PP literal 0 HcmV?d00001 diff --git a/src/assets/styles/variables/_z-index.scss b/src/assets/styles/variables/_z-index.scss index a3c328b..65ac93e 100644 --- a/src/assets/styles/variables/_z-index.scss +++ b/src/assets/styles/variables/_z-index.scss @@ -5,3 +5,7 @@ $modal-background: 98; //layout $nav: 1; $main: 0; + +//marker on map +$icon: 1; +$index: 0; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/MarkersAndPolyline.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/MarkersAndPolyline.module.scss new file mode 100644 index 0000000..f62e86b --- /dev/null +++ b/src/components/domains/detail/course/TravelCourseOnMap/MarkersAndPolyline.module.scss @@ -0,0 +1,21 @@ +.marker { + position: relative; + + .icon { + position: relative; + width: 3.6rem; + z-index: $icon; + } + + .order { + @include text-style(1.2, 700, #ff7c33); + @include flexbox(); + @include pos-center-x(); + top: 0.25rem; + z-index: $index; + background: white; + border-radius: 50%; + width: 2.4rem; + height: 2.4rem; + } +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx index 762206b..a506b0b 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx +++ b/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx @@ -1,8 +1,15 @@ import { CourseDestination } from "@/api/course/register"; import { Location, LocationWithId } from "@/type/type"; import { useEffect } from "react"; -import { Map, MapMarker, Polyline, useMap } from "react-kakao-maps-sdk"; +import { CustomOverlayMap, Map, Polyline, useMap } from "react-kakao-maps-sdk"; import TransitChangeToggle from "./TransitChangeToggle"; + +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import classNames from "classnames/bind"; +import styles from "./MarkersAndPolyline.module.scss"; + +const cx = classNames.bind(styles); interface TravelMapProps { destinations: CourseDestination[]; selectedLocation: LocationWithId | null; @@ -48,12 +55,17 @@ const MarkersAndPolyline = ({ positions, selectedLocation }: MarkersAndPolylineP return ( <> {positions.map((position, index) => ( - -
{`Marker ${index + 1}`}
-
+ +
+
+ +
+
{index + 1}
+
+
))} - + ); }; diff --git a/src/constants/images.ts b/src/constants/images.ts index 244de37..783ef00 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -53,6 +53,7 @@ import memberIcon from "@/assets/images/member.svg"; import memberWhiteIcon from "@/assets/images/member_white.svg"; import modalClose from "@/assets/images/modal_close.svg"; import PartyIcon from "@/assets/images/party.svg"; +import markerInMap from "@/assets/images/pin_on_map.png"; import plus from "@/assets/images/plus.svg"; import rangeLine from "@/assets/images/range_line.svg"; import Rectangle_40 from "@/assets/images/Rectangle_40.svg"; @@ -365,4 +366,8 @@ export const IMAGES = { src: whiteBus, alt: "대중교통 아이콘 미선택", }, + markerInMap: { + src: markerInMap, + alt: "마커", + }, }; From 727828ee8eae4779c305825cae8a825b4fc58488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Wed, 11 Sep 2024 01:04:48 +0900 Subject: [PATCH 011/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B4=80=EB=A0=A8=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=98=EC=9D=91=ED=98=95,=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfoAndReviewTab.module.scss | 5 ++ .../domains/detail/InfoAndReviewTab/index.tsx | 2 +- .../course/CourseInfo/CourseInfo.module.scss | 10 +++ .../detail/course/CourseInfo/CourseInfo.tsx | 34 ++++++++ .../TravelCourseOnMap/DateTab.module.scss | 0 .../TravelCourseOnMap/DateTab.tsx | 0 .../DestinationCard.module.scss | 18 ++++- .../TravelCourseOnMap/DestinationCard.tsx | 0 .../DestinationList.module.scss | 0 .../TravelCourseOnMap/DestinationList.tsx | 61 +++++++++++++++ .../MarkersAndPolyline.module.scss | 0 .../TransitChangeToggle.module.scss | 0 .../TravelCourseOnMap/TransitChangeToggle.tsx | 0 .../TransitTimeChip.module.scss | 0 .../TravelCourseOnMap/TransitTimeChip.tsx | 5 +- .../TravelCourseOnMap.module.scss | 32 ++++++++ .../TravelCourseOnMap/TravelMap.tsx | 16 ++-- .../CourseInfo/TravelCourseOnMap/index.tsx | 77 +++++++++++++++++++ .../TravelDurationAndGroup.module.scss | 42 ++++++++++ .../TravelDurationAndGroup.tsx | 23 ++---- .../TravelCourseOnMap/DestinationList.tsx | 50 ------------ .../TravelCourseOnMap.module.scss | 0 .../detail/course/TravelCourseOnMap/index.tsx | 61 --------------- .../TravelDurationAndGroup.module.scss | 22 ------ .../DetailPageLayout.module.scss | 5 ++ src/layout/DetailPageLayout/index.tsx | 4 +- src/pages/CourseDetailPage/index.tsx | 6 +- 27 files changed, 307 insertions(+), 166 deletions(-) create mode 100644 src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss create mode 100644 src/components/domains/detail/course/CourseInfo/CourseInfo.tsx rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/DateTab.module.scss (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/DateTab.tsx (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/DestinationCard.module.scss (60%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/DestinationCard.tsx (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/DestinationList.module.scss (100%) create mode 100644 src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/MarkersAndPolyline.module.scss (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/TransitChangeToggle.module.scss (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/TransitChangeToggle.tsx (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/TransitTimeChip.module.scss (100%) rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/TransitTimeChip.tsx (82%) create mode 100644 src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss rename src/components/domains/detail/course/{ => CourseInfo}/TravelCourseOnMap/TravelMap.tsx (81%) create mode 100644 src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/index.tsx create mode 100644 src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.module.scss rename src/components/domains/detail/course/{ => CourseInfo}/TravelDurationAndGroup/TravelDurationAndGroup.tsx (55%) delete mode 100644 src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx delete mode 100644 src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.module.scss delete mode 100644 src/components/domains/detail/course/TravelCourseOnMap/index.tsx delete mode 100644 src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss diff --git a/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss b/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss index 907a242..593a44e 100644 --- a/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss +++ b/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss @@ -5,6 +5,11 @@ border-bottom: solid 0.1rem $gray20; padding-bottom: 2rem; + @include responsive("M") { + font-size: 1.4rem; + padding-bottom: 1.5rem; + } + &-btn { cursor: pointer; diff --git a/src/components/domains/detail/InfoAndReviewTab/index.tsx b/src/components/domains/detail/InfoAndReviewTab/index.tsx index 7e248c9..f875412 100644 --- a/src/components/domains/detail/InfoAndReviewTab/index.tsx +++ b/src/components/domains/detail/InfoAndReviewTab/index.tsx @@ -47,7 +47,7 @@ const InfoAndReviewTab = ({ type, info }: InfoAndReviewTabProps) => { 리뷰({totalReviewCount}개)
- {tab === "info" ?
{info}
:
리뷰
} + {tab === "info" ?
{info}
:
리뷰
} ); }; diff --git a/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss b/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss new file mode 100644 index 0000000..ef2000b --- /dev/null +++ b/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss @@ -0,0 +1,10 @@ +.container { + @include column-flexbox(); + gap: 5rem; + padding: 5rem 0; + + @include responsive("M") { + gap: 2rem; + padding: 2rem 0; + } +} diff --git a/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx b/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx new file mode 100644 index 0000000..f29821e --- /dev/null +++ b/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx @@ -0,0 +1,34 @@ +import { getCourseDetail } from "@/api/course"; +import { defaultCourseDetail } from "@/constants/defaultValues"; +import { useQuery } from "@tanstack/react-query"; +import classNames from "classnames/bind"; +import { useParams } from "react-router-dom"; +import styles from "./CourseInfo.module.scss"; +import TravelCourseOnMap from "./TravelCourseOnMap"; +import TravelDurationAndGroup from "./TravelDurationAndGroup/TravelDurationAndGroup"; + +const cx = classNames.bind(styles); + +const CourseInfo = () => { + const { id } = useParams<{ id: string }>(); + + const { data: courseDetailData } = useQuery({ + queryKey: ["courseDetailData", id], + queryFn: () => getCourseDetail(Number(id)), + retry: 0, + }); + + const courseDetail = courseDetailData ?? defaultCourseDetail; + + return ( +
+
+ +
+
+ +
+
+ ); +}; +export default CourseInfo; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DateTab.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DateTab.module.scss similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/DateTab.module.scss rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DateTab.module.scss diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DateTab.tsx similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/DateTab.tsx rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DateTab.tsx diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss similarity index 60% rename from src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss index 998d3e7..dcc9944 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss @@ -1,13 +1,16 @@ .container { + @include flexbox(start, center); background-color: $white; padding: 1.2rem 2rem; border-radius: 1rem; box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.08); + width: 30rem; cursor: pointer; .content { @include flexbox(stretch, center); gap: 2rem; + width: 100%; .icon { width: 3.5rem; @@ -17,18 +20,31 @@ &-num { @include text-style(1.2, 700, $black30); @include pos-center-x(); - top: 1rem; + top: 1.05rem; } } .text { + width: 100%; + overflow: hidden; + &-title { @include text-style(1.4, 700, $black30); + width: 100%; padding-bottom: 0.5rem; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; } &-address { + width: 100%; @include text-style(1.2, 500, $black30); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; } } } diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.tsx similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/DestinationCard.tsx rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.tsx diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/DestinationList.module.scss rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx new file mode 100644 index 0000000..9d8e8e5 --- /dev/null +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx @@ -0,0 +1,61 @@ +import { CourseDestination } from "@/api/course/register"; +import { LocationWithId } from "@/type/type"; +import classNames from "classnames/bind"; +import DateTab from "./DateTab"; +import DestinationCard from "./DestinationCard"; +import styles from "./DestinationList.module.scss"; +import TransitTimeChip from "./TransitTimeChip"; + +const cx = classNames.bind(styles); + +interface DestinationListProps { + duration: number; + selectedDestinations: CourseDestination[]; + selectedDate: number; + onCardClick: (day: number) => void; + onChipClick: (index: number) => void; + selectedLocation: LocationWithId | null; + setSelectedLocation: React.Dispatch>; +} + +const DestinationList = ({ + duration, + selectedDestinations, + selectedDate, + onCardClick, + onChipClick, + selectedLocation, + setSelectedLocation, +}: DestinationListProps) => { + const days = Array.from({ length: duration }, (_, i) => i + 1); + + return ( +
+ +

여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

+
+ {selectedDestinations.map((destination, index) => ( +
+ { + setSelectedLocation({ + id: destination.destination.id, + lat: destination.destination.location.latitude, + lng: destination.destination.location.longitude, + }); + }} + /> + {selectedDestinations.length - 1 !== index && ( + onChipClick(index)} /> + )} +
+ ))} +
+
+ ); +}; +export default DestinationList; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/MarkersAndPolyline.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/MarkersAndPolyline.module.scss similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/MarkersAndPolyline.module.scss rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/MarkersAndPolyline.module.scss diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitChangeToggle.module.scss similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.module.scss rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitChangeToggle.module.scss diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitChangeToggle.tsx similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/TransitChangeToggle.tsx rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitChangeToggle.tsx diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.module.scss similarity index 100% rename from src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.module.scss rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.module.scss diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx similarity index 82% rename from src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx index 97add6a..a9af0f8 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TransitTimeChip.tsx +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx @@ -8,11 +8,12 @@ const cx = classNames.bind(styles); interface TransitTimeChipProps { transit: "private" | "public"; time: string; + onClick: () => void; } -const TransitTimeChip = ({ transit, time }: TransitTimeChipProps) => { +const TransitTimeChip = ({ transit, time, onClick }: TransitTimeChipProps) => { return ( -
+
{transit === "private" ? : } diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss new file mode 100644 index 0000000..a936375 --- /dev/null +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss @@ -0,0 +1,32 @@ +.container { + @include flexbox(center, start); + gap: 1rem; + + @include responsive("T") { + @include column-flexbox(); + flex-direction: column-reverse; + gap: 3rem; + } + + .destination-list { + width: 30rem; + height: 35rem; + } + + .map { + width: 35rem; + height: 35rem; + border-radius: 1rem; + overflow: hidden; + + @include responsive("T") { + width: 50rem; + height: 30rem; + } + + @include responsive("M") { + width: 100%; + height: 20rem; + } + } +} diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx similarity index 81% rename from src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx rename to src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx index a506b0b..0505203 100644 --- a/src/components/domains/detail/course/TravelCourseOnMap/TravelMap.tsx +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx @@ -1,8 +1,7 @@ import { CourseDestination } from "@/api/course/register"; -import { Location, LocationWithId } from "@/type/type"; +import { LocationWithId } from "@/type/type"; import { useEffect } from "react"; import { CustomOverlayMap, Map, Polyline, useMap } from "react-kakao-maps-sdk"; -import TransitChangeToggle from "./TransitChangeToggle"; import Image from "@/components/commons/Image"; import { IMAGES } from "@/constants/images"; @@ -13,12 +12,13 @@ const cx = classNames.bind(styles); interface TravelMapProps { destinations: CourseDestination[]; selectedLocation: LocationWithId | null; - selectedTransit: "car" | "bus"; - onClick: () => void; + // selectedTransit: "car" | "bus"; + // onClick: () => void; } -const TravelMap = ({ destinations, selectedLocation, selectedTransit, onClick }: TravelMapProps) => { +const TravelMap = ({ destinations, selectedLocation }: TravelMapProps) => { const positions = destinations.map((destination) => ({ + id: destination.visitOrder, lat: destination.destination.location.latitude, lng: destination.destination.location.longitude, })); @@ -28,13 +28,13 @@ const TravelMap = ({ destinations, selectedLocation, selectedTransit, onClick }: - + {/* */} ); }; interface MarkersAndPolylineProps { - positions: Location[]; + positions: LocationWithId[]; selectedLocation: LocationWithId | null; } @@ -55,7 +55,7 @@ const MarkersAndPolyline = ({ positions, selectedLocation }: MarkersAndPolylineP return ( <> {positions.map((position, index) => ( - +
diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/index.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/index.tsx new file mode 100644 index 0000000..0a82e54 --- /dev/null +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/index.tsx @@ -0,0 +1,77 @@ +import { Course } from "@/api/course/type"; +import { LocationWithId } from "@/type/type"; +import { useState } from "react"; + +import classNames from "classnames/bind"; +import DestinationList from "./DestinationList"; +import styles from "./TravelCourseOnMap.module.scss"; +import TravelMap from "./TravelMap"; + +const cx = classNames.bind(styles); + +interface TravelCourseOnMapProps { + courseDetail: Course; +} + +const TravelCourseOnMap = ({ courseDetail }: TravelCourseOnMapProps) => { + const [selectedDate, setSelectedDate] = useState(1); + const [selectedLocation, setSelectedLocation] = useState(null); + // const [selectedTransit, setSelectedTransit] = useState<"car" | "bus">("car"); + + const handleDestinationClick = (day: number) => { + setSelectedDate(day); + setSelectedLocation(null); + }; + + // const handleTransitClick = () => { + // if (selectedTransit === "car") setSelectedTransit("bus"); + // else if (selectedTransit === "bus") setSelectedTransit("car"); + // }; + + const selectedDestinations = courseDetail.courseDestinations.filter( + (destination) => destination.date === selectedDate, + ); + + const handleChipClick = (index: number) => { + const nextPositionIndex = index + 1; + if (nextPositionIndex < selectedDestinations.length) { + const currentDestination = selectedDestinations[index]; + const nextDestination = selectedDestinations[nextPositionIndex]; + + const kakaoMapLink = `https://map.kakao.com/?sName=${encodeURIComponent( + currentDestination.destination.location.address, + )}&eName=${encodeURIComponent(nextDestination.destination.location.address)}`; + + window.open(kakaoMapLink, "_blank"); + } else { + alert("마지막 목적지입니다. 다음 목적지가 없습니다."); + } + }; + + return ( +
+
+ +
+
+ {selectedDestinations.length > 0 && ( + + )} +
+
+ ); +}; +export default TravelCourseOnMap; diff --git a/src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.module.scss b/src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.module.scss new file mode 100644 index 0000000..5c8312f --- /dev/null +++ b/src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.module.scss @@ -0,0 +1,42 @@ +.container { + @include flexbox(center, center); + gap: 14rem; + + @include responsive("M") { + gap: 2rem; + } + + .element { + @include flexbox(center, center); + gap: 2rem; + + @include responsive("M") { + gap: 1.3rem; + } + + .icon { + width: 7rem; + + @include responsive("M") { + width: 4rem; + } + } + + .text { + @include column-flexbox(center, start); + @include text-style(1.3, 500, $black30); + + @include responsive("M") { + font-size: 1.2rem; + } + + &-stress { + @include text-style(1.6, 700, $black30); + + @include responsive("M") { + font-size: 1.4rem; + } + } + } + } +} diff --git a/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx b/src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.tsx similarity index 55% rename from src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx rename to src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.tsx index d2117e3..0e84620 100644 --- a/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.tsx +++ b/src/components/domains/detail/course/CourseInfo/TravelDurationAndGroup/TravelDurationAndGroup.tsx @@ -1,25 +1,16 @@ -import { getCourseDetail } from "@/api/course"; import Image from "@/components/commons/Image"; -import { defaultCourseDetail } from "@/constants/defaultValues"; import { IMAGES } from "@/constants/images"; -import { useQuery } from "@tanstack/react-query"; import classNames from "classnames/bind"; -import { useParams } from "react-router-dom"; import styles from "./TravelDurationAndGroup.module.scss"; const cx = classNames.bind(styles); -const TravelDurationAndGroup = () => { - const { id } = useParams<{ id: string }>(); - - const { data: courseDetailData } = useQuery({ - queryKey: ["courseDetailData", id], - queryFn: () => getCourseDetail(Number(id)), - retry: 0, - }); - - const courseDetail = courseDetailData ?? defaultCourseDetail; +interface TravelDurationAndGroupProps { + travelerCount: number; + duration: number; +} +const TravelDurationAndGroup = ({ travelerCount, duration }: TravelDurationAndGroupProps) => { return (
  • @@ -28,7 +19,7 @@ const TravelDurationAndGroup = () => {

여행추천인원

-

{courseDetail.travelerCount}인

+

{travelerCount}인

  • @@ -37,7 +28,7 @@ const TravelDurationAndGroup = () => {
  • 여행기간

    -

    {courseDetail.duration}일

    +

    {duration}일

    diff --git a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx b/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx deleted file mode 100644 index c2502fe..0000000 --- a/src/components/domains/detail/course/TravelCourseOnMap/DestinationList.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { CourseDestination } from "@/api/course/register"; -import { LocationWithId } from "@/type/type"; -import DateTab from "./DateTab"; -import DestinationCard from "./DestinationCard"; - -interface DestinationListProps { - duration: number; - selectedDestinations: CourseDestination[]; - selectedDate: number; - onClick: (day: number) => void; - selectedLocation: LocationWithId | null; - setSelectedLocation: React.Dispatch>; -} - -const DestinationList = ({ - duration, - selectedDestinations, - selectedDate, - onClick, - selectedLocation, - setSelectedLocation, -}: DestinationListProps) => { - const days = Array.from({ length: duration }, (_, i) => i + 1); - - return ( - <> - -

    여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

    -
    - {selectedDestinations.map((destination) => ( - { - setSelectedLocation({ - id: destination.destination.id, - lat: destination.destination.location.latitude, - lng: destination.destination.location.longitude, - }); - }} - /> - ))} -
    - - ); -}; -export default DestinationList; diff --git a/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.module.scss b/src/components/domains/detail/course/TravelCourseOnMap/TravelCourseOnMap.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx b/src/components/domains/detail/course/TravelCourseOnMap/index.tsx deleted file mode 100644 index 6dbc27e..0000000 --- a/src/components/domains/detail/course/TravelCourseOnMap/index.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { getCourseDetail } from "@/api/course"; -import { defaultCourseDetail } from "@/constants/defaultValues"; -import { LocationWithId } from "@/type/type"; -import { useQuery } from "@tanstack/react-query"; -import { useState } from "react"; -import { useParams } from "react-router-dom"; - -import DestinationList from "./DestinationList"; -import TravelMap from "./TravelMap"; - -const TravelCourseOnMap = () => { - const [selectedDate, setSelectedDate] = useState(1); - const [selectedLocation, setSelectedLocation] = useState(null); - const [selectedTransit, setSelectedTransit] = useState<"car" | "bus">("car"); - - const handleDestinationClick = (day: number) => { - setSelectedDate(day); - setSelectedLocation(null); - }; - - const handleTransitClick = () => { - if (selectedTransit === "car") setSelectedTransit("bus"); - else if (selectedTransit === "bus") setSelectedTransit("car"); - }; - - const { id } = useParams<{ id: string }>(); - - const { data: courseDetailData } = useQuery({ - queryKey: ["courseDetailData", id], - queryFn: () => getCourseDetail(Number(id)), - retry: 0, - }); - - const courseDetail = courseDetailData ?? defaultCourseDetail; - - const selectedDestinations = courseDetail.courseDestinations.filter( - (destination) => destination.date === selectedDate, - ); - - return ( - <> - - {selectedDestinations.length > 0 && ( - - )} - - ); -}; -export default TravelCourseOnMap; diff --git a/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss b/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss deleted file mode 100644 index 7df9e50..0000000 --- a/src/components/domains/detail/course/TravelDurationAndGroup/TravelDurationAndGroup.module.scss +++ /dev/null @@ -1,22 +0,0 @@ -.container { - @include flexbox(around, center); - gap: 14.5rem; - - .element { - @include flexbox(center, center); - gap: 2rem; - - .icon { - width: 5.7rem; - } - - .text { - @include column-flexbox(center, start); - @include text-style(1.3, 500, $black30); - - &-stress { - @include text-style(1.6, 700, $black30); - } - } - } -} diff --git a/src/layout/DetailPageLayout/DetailPageLayout.module.scss b/src/layout/DetailPageLayout/DetailPageLayout.module.scss index a359a22..b46ede3 100644 --- a/src/layout/DetailPageLayout/DetailPageLayout.module.scss +++ b/src/layout/DetailPageLayout/DetailPageLayout.module.scss @@ -11,4 +11,9 @@ padding: 0 1rem; } } + + .article { + padding: 5rem 0 10rem; + width: 100%; + } } diff --git a/src/layout/DetailPageLayout/index.tsx b/src/layout/DetailPageLayout/index.tsx index d2f7416..7ae51ef 100644 --- a/src/layout/DetailPageLayout/index.tsx +++ b/src/layout/DetailPageLayout/index.tsx @@ -18,7 +18,9 @@ const DetailPageLayout = ({ type, header, main, info }: DetailPageLayoutProps) =
    {header}
    {main}
    - +
    + +
    ); }; diff --git a/src/pages/CourseDetailPage/index.tsx b/src/pages/CourseDetailPage/index.tsx index 53a821b..ebcaf65 100644 --- a/src/pages/CourseDetailPage/index.tsx +++ b/src/pages/CourseDetailPage/index.tsx @@ -1,12 +1,10 @@ import CourseHeader from "@/components/domains/detail/course/CourseHeader/CourseHeader"; +import CourseInfo from "@/components/domains/detail/course/CourseInfo/CourseInfo"; import CourseMain from "@/components/domains/detail/course/CourseMain/CourseMain"; -import TravelCourseOnMap from "@/components/domains/detail/course/TravelCourseOnMap"; import DetailPageLayout from "@/layout/DetailPageLayout"; const CourseDetailPage = () => { - return ( - } main={} info={} /> - ); + return } main={} info={} />; }; export default CourseDetailPage; From 81c4df8d325d9896657e2898a4ff150be7b72fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Wed, 11 Sep 2024 23:53:02 +0900 Subject: [PATCH 012/131] =?UTF-8?q?Fix:=20=EC=B9=B4=EB=93=9C=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=BC=EC=9E=90=20=EC=9E=98=EB=A6=BC=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EB=B0=8F=20=EC=8A=A4=ED=81=AC=EB=A1=A4=EB=B0=94=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/styles/main.scss | 1 + src/assets/styles/mixins/_scroll-bar.scss | 21 +++++++++++++++++++ .../commons/CardList/AllCardList.module.scss | 9 +++----- .../commons/CardList/AllCardList.tsx | 6 ++++-- .../course/CourseMain/CourseMain.module.scss | 1 - 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 src/assets/styles/mixins/_scroll-bar.scss diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss index 824e28f..cd3c4e4 100644 --- a/src/assets/styles/main.scss +++ b/src/assets/styles/main.scss @@ -8,3 +8,4 @@ @import "@/assets/styles/mixins/responsive"; @import "@/assets/styles/mixins/text-style"; @import "@/assets/styles/mixins/extend"; +@import "@/assets/styles/mixins/scroll-bar"; diff --git a/src/assets/styles/mixins/_scroll-bar.scss b/src/assets/styles/mixins/_scroll-bar.scss new file mode 100644 index 0000000..5278c25 --- /dev/null +++ b/src/assets/styles/mixins/_scroll-bar.scss @@ -0,0 +1,21 @@ +@mixin custom-scroll-bar($width, $height) { + &::-webkit-scrollbar { + width: $width * 1rem; + height: $height * 1rem; + } + + &::-webkit-scrollbar-track { + background: $white; + border-radius: 1rem; + } + + &::-webkit-scrollbar-thumb { + background-color: $gray30; + border-radius: 1rem; + border: 0.3rem solid $white; + } + + &::-webkit-scrollbar-thumb:hover { + background-color: $black10; + } +} diff --git a/src/components/commons/CardList/AllCardList.module.scss b/src/components/commons/CardList/AllCardList.module.scss index 38262a0..6b83654 100644 --- a/src/components/commons/CardList/AllCardList.module.scss +++ b/src/components/commons/CardList/AllCardList.module.scss @@ -1,8 +1,9 @@ .list-container { + @include custom-scroll-bar(1.2, 1.2); @include column-flexbox($jc: start, $ai: start); height: 40rem; overflow-y: scroll; - overflow-x: hidden; + padding: 1.5rem; @include responsive("T") { max-height: 40rem; @@ -19,11 +20,7 @@ gap: 1.8rem; width: 100%; - & > a { - width: 100%; - } - - & > div { + .card-link { width: 100%; } } diff --git a/src/components/commons/CardList/AllCardList.tsx b/src/components/commons/CardList/AllCardList.tsx index bff4790..17d9af6 100644 --- a/src/components/commons/CardList/AllCardList.tsx +++ b/src/components/commons/CardList/AllCardList.tsx @@ -26,11 +26,13 @@ export const AllCardList = ({ destinations, useLink = false }: AllCardListProps)
    {destinations.map((item) => useLink ? ( - + ) : ( - +
    + +
    ), )}
    diff --git a/src/components/domains/detail/course/CourseMain/CourseMain.module.scss b/src/components/domains/detail/course/CourseMain/CourseMain.module.scss index f6f7e04..258b65c 100644 --- a/src/components/domains/detail/course/CourseMain/CourseMain.module.scss +++ b/src/components/domains/detail/course/CourseMain/CourseMain.module.scss @@ -1,6 +1,5 @@ .course-detail-info { @include flexbox($ai: end); - gap: 1rem; @include responsive("T") { @include column-flexbox(center, center); From 78b2108c56e10218ffd8af1e19a05592583862e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Thu, 12 Sep 2024 02:23:03 +0900 Subject: [PATCH 013/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=B9=B4=EB=93=9C=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=B0=98=EC=9D=91=ED=98=95=20UI=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/gray_sign.svg | 12 +++++++++ .../CardContent/CardContent.module.scss | 24 +++++++++++++----- .../commons/CardList/AllCardList.module.scss | 1 + .../DestinationList.module.scss | 25 +++++++++++++++++++ .../TravelCourseOnMap/DestinationList.tsx | 10 ++++---- .../TransitTimeChip.module.scss | 4 +-- .../TravelCourseOnMap/TransitTimeChip.tsx | 8 +++--- .../TravelCourseOnMap.module.scss | 7 +++--- .../course/CourseMain/CourseMain.module.scss | 4 +++ src/constants/images.ts | 5 ++++ 10 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 src/assets/images/gray_sign.svg diff --git a/src/assets/images/gray_sign.svg b/src/assets/images/gray_sign.svg new file mode 100644 index 0000000..401aba7 --- /dev/null +++ b/src/assets/images/gray_sign.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/components/commons/CardContent/CardContent.module.scss b/src/components/commons/CardContent/CardContent.module.scss index fd8091f..53e570d 100644 --- a/src/components/commons/CardContent/CardContent.module.scss +++ b/src/components/commons/CardContent/CardContent.module.scss @@ -14,10 +14,11 @@ } &__image-container { - width: 40%; + width: 35%; height: 100%; overflow: hidden; border-radius: 0.5rem; + flex-shrink: 0; } &__image { @@ -29,17 +30,16 @@ &__info { @include column-flexbox($jc: between, $ai: start); width: 100%; - gap: 3rem; - - @include responsive("M") { - gap: 2.5rem; - } + height: 100%; + overflow: hidden; } &__details { @include column-flexbox($jc: start, $ai: start); gap: 0.5rem; padding-top: 0.5rem; + width: 100%; + overflow: hidden; @include responsive("M") { gap: 0.3rem; @@ -48,6 +48,12 @@ &__name { @include text-style(1.6, 700, $black30); + width: 100%; + padding-bottom: 0.5rem; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; @include responsive("M") { font-size: 1.4rem; @@ -56,6 +62,12 @@ &__address { @include text-style(1.2, 500, $black30); + width: 100%; + padding-bottom: 0.5rem; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; @include responsive("M") { font-size: 1.1rem; diff --git a/src/components/commons/CardList/AllCardList.module.scss b/src/components/commons/CardList/AllCardList.module.scss index 6b83654..e368a86 100644 --- a/src/components/commons/CardList/AllCardList.module.scss +++ b/src/components/commons/CardList/AllCardList.module.scss @@ -12,6 +12,7 @@ @include responsive("M") { max-height: 35rem; + padding: 1.5rem 1rem; } .list-box { diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss index e69de29..0848689 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss @@ -0,0 +1,25 @@ +.container { + @include column-flexbox(); + gap: 1rem; + + .text { + @include text-style(1.2, 400, #8c8c8c); + + @include responsive("M") { + font-size: 1.1rem; + } + } + + .cards { + @include custom-scroll-bar(1.2, 1.2); + max-height: 33rem; + overflow-y: scroll; + padding: 1.5rem 1rem 1.5rem 1.5rem; + + .card { + @include column-flexbox(); + gap: 0.5rem; + padding-bottom: 0.5rem; + } + } +} diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx index 9d8e8e5..b458449 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.tsx @@ -31,8 +31,10 @@ const DestinationList = ({ return (
    - -

    여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

    +
    + +
    +

    여행지를 클릭하면 여행지 위치를 확인할 수 있습니다.

    {selectedDestinations.map((destination, index) => (
    @@ -49,9 +51,7 @@ const DestinationList = ({ }); }} /> - {selectedDestinations.length - 1 !== index && ( - onChipClick(index)} /> - )} + {selectedDestinations.length - 1 !== index && onChipClick(index)} />}
    ))}
    diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.module.scss index d0a8517..1082b72 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.module.scss @@ -2,12 +2,12 @@ @include inline-flexbox(); background-color: $primary10; border-radius: 1rem; - padding: 0.8rem 1rem; + padding: 0.8rem 0.8rem; cursor: pointer; .content { @include flexbox(); - gap: 0.5rem; + gap: 0.3rem; .icon { @include flexbox(); diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx index a9af0f8..1164a3d 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TransitTimeChip.tsx @@ -6,19 +6,17 @@ import styles from "./TransitTimeChip.module.scss"; const cx = classNames.bind(styles); interface TransitTimeChipProps { - transit: "private" | "public"; - time: string; onClick: () => void; } -const TransitTimeChip = ({ transit, time, onClick }: TransitTimeChipProps) => { +const TransitTimeChip = ({ onClick }: TransitTimeChipProps) => { return (
    - {transit === "private" ? : } +
    -

    {time}

    +

    {"길찾기"}

    diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss index a936375..edcf24a 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss @@ -9,13 +9,12 @@ } .destination-list { - width: 30rem; - height: 35rem; + width: 32rem; } .map { - width: 35rem; - height: 35rem; + width: 40rem; + height: 40rem; border-radius: 1rem; overflow: hidden; diff --git a/src/components/domains/detail/course/CourseMain/CourseMain.module.scss b/src/components/domains/detail/course/CourseMain/CourseMain.module.scss index 258b65c..b5c456a 100644 --- a/src/components/domains/detail/course/CourseMain/CourseMain.module.scss +++ b/src/components/domains/detail/course/CourseMain/CourseMain.module.scss @@ -7,6 +7,10 @@ max-width: 50rem; } + @include responsive("M") { + max-width: 40rem; + } + &__main-image-container { border-radius: 2rem; overflow: hidden; diff --git a/src/constants/images.ts b/src/constants/images.ts index 783ef00..d67b5c7 100644 --- a/src/constants/images.ts +++ b/src/constants/images.ts @@ -38,6 +38,7 @@ import GrayKaKaoIcon from "@/assets/images/gray_kakao.svg"; import GrayLinkIcon from "@/assets/images/gray_link.svg"; import GrayPhotoIcon from "@/assets/images/gray_photo.svg"; import GraySearchIcon from "@/assets/images/gray_search.svg"; +import graySign from "@/assets/images/gray_sign.svg"; import GrayStarIcon from "@/assets/images/gray_star.svg"; import grayTransitTriangle from "@/assets/images/gray_transit_triangle.svg"; import grayTriangle from "@/assets/images/gray_triangle.svg"; @@ -370,4 +371,8 @@ export const IMAGES = { src: markerInMap, alt: "마커", }, + graySign: { + src: graySign, + alt: "길찾기 아이콘", + }, }; From 3bc9a3046a05fc4f8a3c4741579dc31376b9ce1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Thu, 12 Sep 2024 03:42:50 +0900 Subject: [PATCH 014/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=A7=80=EB=8F=84=20=EB=B0=8F=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EA=B4=80=EB=A0=A8=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfoAndReviewTab/InfoAndReviewTab.module.scss | 4 ++++ .../domains/detail/InfoAndReviewTab/index.tsx | 6 +++++- .../detail/course/CourseInfo/CourseInfo.module.scss | 8 ++++++++ .../domains/detail/course/CourseInfo/CourseInfo.tsx | 4 ++-- .../TravelCourseOnMap/DestinationCard.module.scss | 4 ++++ .../TravelCourseOnMap/DestinationList.module.scss | 11 +++++++++-- .../TravelCourseOnMap/TravelCourseOnMap.module.scss | 11 +++++++++-- .../course/CourseInfo/TravelCourseOnMap/TravelMap.tsx | 2 +- 8 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss b/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss index 593a44e..5799acd 100644 --- a/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss +++ b/src/components/domains/detail/InfoAndReviewTab/InfoAndReviewTab.module.scss @@ -18,3 +18,7 @@ } } } + +.section { + width: 100%; +} diff --git a/src/components/domains/detail/InfoAndReviewTab/index.tsx b/src/components/domains/detail/InfoAndReviewTab/index.tsx index f875412..aa1b243 100644 --- a/src/components/domains/detail/InfoAndReviewTab/index.tsx +++ b/src/components/domains/detail/InfoAndReviewTab/index.tsx @@ -47,7 +47,11 @@ const InfoAndReviewTab = ({ type, info }: InfoAndReviewTabProps) => { 리뷰({totalReviewCount}개)
    - {tab === "info" ?
    {info}
    :
    리뷰
    } + {tab === "info" ? ( +
    {info}
    + ) : ( +
    리뷰
    + )} ); }; diff --git a/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss b/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss index ef2000b..03b5b5e 100644 --- a/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss +++ b/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss @@ -7,4 +7,12 @@ gap: 2rem; padding: 2rem 0; } + + .info { + width: 100%; + } + + .course { + width: 100%; + } } diff --git a/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx b/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx index f29821e..ba0e80c 100644 --- a/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx +++ b/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx @@ -22,10 +22,10 @@ const CourseInfo = () => { return (
    -
    +
    -
    +
    diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss index dcc9944..dcff342 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationCard.module.scss @@ -7,6 +7,10 @@ width: 30rem; cursor: pointer; + @include responsive("M") { + width: 100%; + } + .content { @include flexbox(stretch, center); gap: 2rem; diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss index 0848689..f6f106c 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/DestinationList.module.scss @@ -1,6 +1,12 @@ .container { - @include column-flexbox(); + @include column-flexbox(start, center); gap: 1rem; + max-height: 50rem; + height: 100%; + + @include responsive("M") { + max-height: 40rem; + } .text { @include text-style(1.2, 400, #8c8c8c); @@ -12,7 +18,7 @@ .cards { @include custom-scroll-bar(1.2, 1.2); - max-height: 33rem; + width: 100%; overflow-y: scroll; padding: 1.5rem 1rem 1.5rem 1.5rem; @@ -20,6 +26,7 @@ @include column-flexbox(); gap: 0.5rem; padding-bottom: 0.5rem; + width: 100%; } } } diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss index edcf24a..a2abb16 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelCourseOnMap.module.scss @@ -1,6 +1,7 @@ .container { @include flexbox(center, start); gap: 1rem; + width: 100%; @include responsive("T") { @include column-flexbox(); @@ -10,11 +11,16 @@ .destination-list { width: 32rem; + + @include responsive("M") { + max-width: 32rem; + width: 100%; + } } .map { - width: 40rem; - height: 40rem; + width: 50rem; + height: 50rem; border-radius: 1rem; overflow: hidden; @@ -24,6 +30,7 @@ } @include responsive("M") { + max-width: 50rem; width: 100%; height: 20rem; } diff --git a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx index 0505203..808ff51 100644 --- a/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx +++ b/src/components/domains/detail/course/CourseInfo/TravelCourseOnMap/TravelMap.tsx @@ -25,7 +25,7 @@ const TravelMap = ({ destinations, selectedLocation }: TravelMapProps) => { return ( <> - + {/* */} From 6422ebf224ee36e0f053dfba8e81e166d2a1369f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Fri, 13 Sep 2024 13:56:58 +0900 Subject: [PATCH 015/131] =?UTF-8?q?Feat:=20=EC=83=81=EC=84=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20description=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Description/Description.module.scss | 11 ++++++++++ .../detail/Description/Description.tsx | 20 +++++++++++++++++++ .../course/CourseInfo/CourseInfo.module.scss | 6 +++++- .../detail/course/CourseInfo/CourseInfo.tsx | 4 ++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/components/domains/detail/Description/Description.module.scss create mode 100644 src/components/domains/detail/Description/Description.tsx diff --git a/src/components/domains/detail/Description/Description.module.scss b/src/components/domains/detail/Description/Description.module.scss new file mode 100644 index 0000000..180043e --- /dev/null +++ b/src/components/domains/detail/Description/Description.module.scss @@ -0,0 +1,11 @@ +.container { + padding: 0 3rem; + + .content { + min-height: 20rem; + border-radius: 1rem; + background-color: $white; + padding: 2rem; + box-shadow: 0px 3px 16px 0px rgba(0, 0, 0, 0.08); + } +} diff --git a/src/components/domains/detail/Description/Description.tsx b/src/components/domains/detail/Description/Description.tsx new file mode 100644 index 0000000..bf7e094 --- /dev/null +++ b/src/components/domains/detail/Description/Description.tsx @@ -0,0 +1,20 @@ +import classNames from "classnames/bind"; +import DOMPurify from "dompurify"; +import styles from "./Description.module.scss"; + +const cx = classNames.bind(styles); + +interface DescriptionProps { + content: string; +} + +const Description = ({ content }: DescriptionProps) => { + const sanitizedContent = { __html: DOMPurify.sanitize(content) }; + + return ( +
    +

    +

    + ); +}; +export default Description; diff --git a/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss b/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss index 03b5b5e..b892946 100644 --- a/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss +++ b/src/components/domains/detail/course/CourseInfo/CourseInfo.module.scss @@ -1,7 +1,7 @@ .container { @include column-flexbox(); gap: 5rem; - padding: 5rem 0; + padding: 5rem 0 0; @include responsive("M") { gap: 2rem; @@ -15,4 +15,8 @@ .course { width: 100%; } + + .description { + width: 100%; + } } diff --git a/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx b/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx index ba0e80c..a58c980 100644 --- a/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx +++ b/src/components/domains/detail/course/CourseInfo/CourseInfo.tsx @@ -3,6 +3,7 @@ import { defaultCourseDetail } from "@/constants/defaultValues"; import { useQuery } from "@tanstack/react-query"; import classNames from "classnames/bind"; import { useParams } from "react-router-dom"; +import Description from "../../Description/Description"; import styles from "./CourseInfo.module.scss"; import TravelCourseOnMap from "./TravelCourseOnMap"; import TravelDurationAndGroup from "./TravelDurationAndGroup/TravelDurationAndGroup"; @@ -28,6 +29,9 @@ const CourseInfo = () => {
    +
    + +
    ); }; From 614b791094f6c4ae787b36a4f16ecb20be6a88c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=95=98=EC=9D=80?= Date: Sat, 14 Sep 2024 21:47:40 +0900 Subject: [PATCH 016/131] =?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=ED=83=AD=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EB=B0=8F=20form=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/add_file.svg | 3 + src/assets/images/blue_star.svg | 11 +++ .../domains/detail/InfoAndReviewTab/index.tsx | 5 +- .../detail/Review/FilterButtons.module.scss | 21 ++++ .../domains/detail/Review/FilterButtons.tsx | 26 +++++ .../detail/Review/ImageInput.module.scss | 59 +++++++++++ .../domains/detail/Review/ImageInput.tsx | 98 +++++++++++++++++++ .../domains/detail/Review/ReviewForm.tsx | 70 +++++++++++++ .../detail/Review/StarRating.module.scss | 21 ++++ .../domains/detail/Review/StarRating.tsx | 51 ++++++++++ .../detail/Review/StarScore.module.scss | 17 ++++ .../domains/detail/Review/StarScore.tsx | 24 +++++ .../detail/Review/TextInput.module.scss | 30 ++++++ .../domains/detail/Review/TextInput.tsx | 30 ++++++ .../domains/detail/Review/index.tsx | 22 +++++ src/constants/images.ts | 10 ++ src/constants/reviewFilter.ts | 8 ++ src/hooks/useFormImageUpload.ts | 18 +++- src/layout/DetailPageLayout/index.tsx | 6 +- src/pages/CourseDetailPage/index.tsx | 11 ++- src/type/type.d.ts | 8 ++ 21 files changed, 539 insertions(+), 10 deletions(-) create mode 100644 src/assets/images/add_file.svg create mode 100644 src/assets/images/blue_star.svg create mode 100644 src/components/domains/detail/Review/FilterButtons.module.scss create mode 100644 src/components/domains/detail/Review/FilterButtons.tsx create mode 100644 src/components/domains/detail/Review/ImageInput.module.scss create mode 100644 src/components/domains/detail/Review/ImageInput.tsx create mode 100644 src/components/domains/detail/Review/ReviewForm.tsx create mode 100644 src/components/domains/detail/Review/StarRating.module.scss create mode 100644 src/components/domains/detail/Review/StarRating.tsx create mode 100644 src/components/domains/detail/Review/StarScore.module.scss create mode 100644 src/components/domains/detail/Review/StarScore.tsx create mode 100644 src/components/domains/detail/Review/TextInput.module.scss create mode 100644 src/components/domains/detail/Review/TextInput.tsx create mode 100644 src/components/domains/detail/Review/index.tsx create mode 100644 src/constants/reviewFilter.ts diff --git a/src/assets/images/add_file.svg b/src/assets/images/add_file.svg new file mode 100644 index 0000000..0fd7660 --- /dev/null +++ b/src/assets/images/add_file.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/blue_star.svg b/src/assets/images/blue_star.svg new file mode 100644 index 0000000..ba5d95d --- /dev/null +++ b/src/assets/images/blue_star.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/components/domains/detail/InfoAndReviewTab/index.tsx b/src/components/domains/detail/InfoAndReviewTab/index.tsx index aa1b243..4c56611 100644 --- a/src/components/domains/detail/InfoAndReviewTab/index.tsx +++ b/src/components/domains/detail/InfoAndReviewTab/index.tsx @@ -14,9 +14,10 @@ type ReviewsResponse = getCourseReviewsResponseDto | getDestinationReviewsRespon interface InfoAndReviewTabProps { type: "course" | "destination"; info: ReactNode; + review: ReactNode; } -const InfoAndReviewTab = ({ type, info }: InfoAndReviewTabProps) => { +const InfoAndReviewTab = ({ type, info, review }: InfoAndReviewTabProps) => { const { id } = useParams(); const postId = Number(id); const [tab, setTab] = useState("info"); @@ -50,7 +51,7 @@ const InfoAndReviewTab = ({ type, info }: InfoAndReviewTabProps) => { {tab === "info" ? (
    {info}
    ) : ( -
    리뷰
    +
    {review}
    )} ); diff --git a/src/components/domains/detail/Review/FilterButtons.module.scss b/src/components/domains/detail/Review/FilterButtons.module.scss new file mode 100644 index 0000000..c22f819 --- /dev/null +++ b/src/components/domains/detail/Review/FilterButtons.module.scss @@ -0,0 +1,21 @@ +.filters { + @include flexbox(center, center); + gap: 0.8rem; + + .filter { + list-style: none; + + &-btn { + @include text-style(1.4, 500, $black30); + background-color: $gray10; + border-radius: 0.6rem; + padding: 0.6rem 1.6rem; + cursor: pointer; + + &.isSelected { + border: solid 0.1rem $primary40; + color: $primary40; + } + } + } +} diff --git a/src/components/domains/detail/Review/FilterButtons.tsx b/src/components/domains/detail/Review/FilterButtons.tsx new file mode 100644 index 0000000..b17837f --- /dev/null +++ b/src/components/domains/detail/Review/FilterButtons.tsx @@ -0,0 +1,26 @@ +import { REVEIW_FILTER } from "@/constants/reviewFilter"; +import { FilterType } from "@/type/type"; +import classNames from "classnames/bind"; +import styles from "./FilterButtons.module.scss"; + +const cx = classNames.bind(styles); + +interface FilterButtonsProps { + selectedFilter: FilterType; + onClick: (filter: FilterType) => void; +} + +const FilterButtons = ({ selectedFilter, onClick }: FilterButtonsProps) => { + return ( +
      + {REVEIW_FILTER.map(({ id, name, type }) => ( +
    • + +
    • + ))} +
    + ); +}; +export default FilterButtons; diff --git a/src/components/domains/detail/Review/ImageInput.module.scss b/src/components/domains/detail/Review/ImageInput.module.scss new file mode 100644 index 0000000..c560141 --- /dev/null +++ b/src/components/domains/detail/Review/ImageInput.module.scss @@ -0,0 +1,59 @@ +.image-input { + .input { + display: none; + } + + .images { + @include flexbox(); + gap: 1rem; + + .add-btn { + @include flexbox(); + border: solid 0.1rem $gray20; + border-radius: 0.6rem; + width: 10rem; + height: 10rem; + cursor: pointer; + + .icon { + width: 3rem; + } + } + } + + .preview { + @include flexbox(); + gap: 0.5rem; + + &-item { + @include flexbox(); + position: relative; + border: solid 0.1rem $gray20; + border-radius: 0.6rem; + overflow: hidden; + width: 10rem; + height: 10rem; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + button { + @include flexbox(); + background-color: $white; + border-radius: 50%; + padding: 0.2rem; + position: absolute; + top: 0.5rem; + right: 0.5rem; + cursor: pointer; + + img { + width: 1.5rem; + } + } + } + } +} diff --git a/src/components/domains/detail/Review/ImageInput.tsx b/src/components/domains/detail/Review/ImageInput.tsx new file mode 100644 index 0000000..8ecb7f3 --- /dev/null +++ b/src/components/domains/detail/Review/ImageInput.tsx @@ -0,0 +1,98 @@ +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import { ReviewFormType } from "@/type/type"; +import classNames from "classnames/bind"; +import { useRef, useState } from "react"; +import { Control, Controller } from "react-hook-form"; +import styles from "./ImageInput.module.scss"; + +const cx = classNames.bind(styles); + +interface ImageInputProps { + control: Control; +} + +const ImageInput = ({ control }: ImageInputProps) => { + const fileInputRef = useRef(null); + const [errorMessage, setErrorMessage] = useState(null); + + return ( + { + const handleClick = () => { + if (field.value && field.value.length === 5) + return setErrorMessage("이미지는 최대 5개까지 업로드할 수 있습니다."); + + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + const handleFileChange = (event: React.ChangeEvent) => { + if (!event.target.files) return; + + const newFiles = Array.from(event.target.files); + const totalFiles = (field.value ? field.value.length : 0) + newFiles.length; + + if (totalFiles > 5) { + setErrorMessage("이미지는 최대 5개까지 업로드할 수 있습니다."); + return; + } + + setErrorMessage(null); + + const updatedFiles = [...(field.value || []), ...newFiles]; + field.onChange(updatedFiles); + }; + + const handleRemoveImage = (index: number) => { + const updatedFiles = (field.value || []).filter((_, i) => i !== index); + field.onChange(updatedFiles); + }; + + return ( + <> +
    + +
    +
    +
    + +
    +
    + +
    + {field.value && + field.value.map((item, index) => ( +
    + {typeof item === "string" ? ( // 기존 이미지 URL인 경우 + {`Preview + ) : ( + {`Preview + )} + +
    + ))} +
    +
    +
    + {errorMessage &&

    {errorMessage}

    } + + ); + }} + /> + ); +}; + +export default ImageInput; diff --git a/src/components/domains/detail/Review/ReviewForm.tsx b/src/components/domains/detail/Review/ReviewForm.tsx new file mode 100644 index 0000000..9786282 --- /dev/null +++ b/src/components/domains/detail/Review/ReviewForm.tsx @@ -0,0 +1,70 @@ +import { useHandleImageUpload } from "@/hooks/useFormImageUpload"; +import { ReviewFormType } from "@/type/type"; +import { FieldValues, SubmitHandler, useForm } from "react-hook-form"; +import ImageInput from "./ImageInput"; +import StarRating from "./StarRating"; +import TextInput from "./TextInput"; + +interface ReviewFormProps { + initialData?: ReviewFormType; +} + +const ReviewForm = ({ initialData }: ReviewFormProps) => { + const { handleImageUpload } = useHandleImageUpload(); + const { handleSubmit, control } = useForm({ + defaultValues: initialData || { + rating: 0, + content: "", + images: null, + }, + }); + + // const queryClient = useQueryClient(); + + // const mutation = useMutation({ + // mutationFn: (formData) => { + // if (initialData) { + // return axios.put(`/api/reviews/${initialData.id}`, formData); + // } else { + // return axios.post("/api/reviews", formData); + // } + // }, + // onSuccess: () => { + // queryClient.invalidateQueries("reviews"); + // alert("리뷰가 성공적으로 등록되었습니다!"); + // }, + // onError: () => { + // alert("리뷰 등록 중 오류가 발생했습니다. 다시 시도해주세요."); + // }, + // }); + + const onSubmit: SubmitHandler = async (data) => { + if (data.images && data.images.length > 0) { + data.images = await handleImageUpload(data.images); + } + console.log(data); + // mutation.mutate(data); + }; + + // useEffect(() => { + // if (initialData) { + // setValue("rating", initialData.rating); + // setValue("content", initialData.content); + // } + // }, [initialData, setValue]); + + return ( +
    + + + + + {/* */} + + + ); +}; + +export default ReviewForm; diff --git a/src/components/domains/detail/Review/StarRating.module.scss b/src/components/domains/detail/Review/StarRating.module.scss new file mode 100644 index 0000000..b0e7ca7 --- /dev/null +++ b/src/components/domains/detail/Review/StarRating.module.scss @@ -0,0 +1,21 @@ +.star-rating { + @include column-flexbox(center, start); + gap: 0.3rem; + + .stars { + @include flexbox(); + gap: 1rem; + + .star { + position: relative; + width: 2rem; + display: block; + cursor: pointer; + } + } + + .helper-text { + @include text-style(1.2, 400, $red); + min-height: 1.6rem; + } +} diff --git a/src/components/domains/detail/Review/StarRating.tsx b/src/components/domains/detail/Review/StarRating.tsx new file mode 100644 index 0000000..4467c90 --- /dev/null +++ b/src/components/domains/detail/Review/StarRating.tsx @@ -0,0 +1,51 @@ +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import { ReviewFormType } from "@/type/type"; +import classNames from "classnames/bind"; +import { useState } from "react"; +import { Control, Controller } from "react-hook-form"; +import styles from "./StarRating.module.scss"; + +const cx = classNames.bind(styles); + +interface StarRatingProps { + control: Control; +} + +const StarRating = ({ control }: StarRatingProps) => { + const [hoveredStar, setHoveredStar] = useState(null); + + return ( + { + if (value) return true; + return "별점을 선택해주세요."; + }, + }, + }} + render={({ field, fieldState }) => ( +
    +
    + {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
    +

    {fieldState.error?.message}

    +
    + )} + /> + ); +}; + +export default StarRating; diff --git a/src/components/domains/detail/Review/StarScore.module.scss b/src/components/domains/detail/Review/StarScore.module.scss new file mode 100644 index 0000000..fe89133 --- /dev/null +++ b/src/components/domains/detail/Review/StarScore.module.scss @@ -0,0 +1,17 @@ +.container { + @include flexbox(); + gap: 0.5rem; + + .icon { + @include flexbox(); + width: 3rem; + } + + .score { + @include text-style(2, 400, $black30); + + &-stress { + font-weight: 700; + } + } +} diff --git a/src/components/domains/detail/Review/StarScore.tsx b/src/components/domains/detail/Review/StarScore.tsx new file mode 100644 index 0000000..d3681d3 --- /dev/null +++ b/src/components/domains/detail/Review/StarScore.tsx @@ -0,0 +1,24 @@ +import Image from "@/components/commons/Image"; +import { IMAGES } from "@/constants/images"; +import classNames from "classnames/bind"; +import styles from "./StarScore.module.scss"; + +const cx = classNames.bind(styles); + +interface StarScoreProps { + score: number; +} + +const StarScore = ({ score }: StarScoreProps) => { + return ( +
    +
    + +
    +
    + {score} / 5 +
    +
    + ); +}; +export default StarScore; diff --git a/src/components/domains/detail/Review/TextInput.module.scss b/src/components/domains/detail/Review/TextInput.module.scss new file mode 100644 index 0000000..21602fb --- /dev/null +++ b/src/components/domains/detail/Review/TextInput.module.scss @@ -0,0 +1,30 @@ +.content { + @include column-flexbox(center, center); + gap: 0.3rem; + width: 100%; + + .text-input { + @include text-style(1.6, 500, $black30); + border: solid 0.1rem $gray20; + border-radius: 0.8rem; + height: 18rem; + padding: 1.2rem 1.6rem; + width: 100%; + + &::placeholder { + color: $gray30; + } + } + .helper-text { + @include flexbox(between, center); + width: 100%; + + .length { + @include text-style(1.5, 500, $gray30); + } + + .error { + @include text-style(1.2, 400, $red); + } + } +} diff --git a/src/components/domains/detail/Review/TextInput.tsx b/src/components/domains/detail/Review/TextInput.tsx new file mode 100644 index 0000000..077e75b --- /dev/null +++ b/src/components/domains/detail/Review/TextInput.tsx @@ -0,0 +1,30 @@ +import { ReviewFormType } from "@/type/type"; +import classNames from "classnames/bind"; +import { Control, Controller } from "react-hook-form"; +import styles from "./TextInput.module.scss"; + +const cx = classNames.bind(styles); + +interface TextInputProps { + control: Control; +} + +const TextInput = ({ control }: TextInputProps) => { + return ( + ( +
    +