diff --git a/src/@types/service.ts b/src/@types/service.ts index cf8f405b..4ea36ae0 100644 --- a/src/@types/service.ts +++ b/src/@types/service.ts @@ -78,15 +78,11 @@ export type subMemberRes = { message: string; data: { tripId: number; - connectedMembers: { - memberId: number; - name: string; - thumbnailUrl: string; - }[]; tripMembers: { memberId: number; name: string; thumbnailUrl: string; + connected: boolean; }[]; numberOfPeople: number; } | null; diff --git a/src/@types/socket.types.ts b/src/@types/socket.types.ts index 9434e981..2a70ed7b 100644 --- a/src/@types/socket.types.ts +++ b/src/@types/socket.types.ts @@ -62,15 +62,11 @@ type subMemberMessage = (response: { message: string; data: { tripId: number; - connectedMembers: { - memberId: number; - name: string; - thumbnailUrl: string; - }[]; tripMembers: { memberId: number; name: string; thumbnailUrl: string; + connected: boolean; }[]; numberOfPeople: number; }; @@ -132,7 +128,7 @@ interface pubDeleteItem { } interface pubMember { - memberId: number; + token: string; } interface pubGetPathAndItems { diff --git a/src/@types/trips.types.ts b/src/@types/trips.types.ts index fb708de8..3197ccfe 100644 --- a/src/@types/trips.types.ts +++ b/src/@types/trips.types.ts @@ -18,3 +18,35 @@ interface MyTripType { area: string; subArea: string; } + +interface ourTripType { + tripLikedItemId: number; + tourItemId: number; + contentTypeId: number; + ratingAverage: number; + reviewCount: number; + smallThumbnailUrl: string; + tourAddress: string; + prefer: boolean; + notPrefer: boolean; + preferTotalCount: number; + notPreferTotalCount: number; + title: string; +} + +interface ThumbsProps { + tripId: number; + tourId: number; + prefer: boolean; + notPrefer: boolean; +} + +interface AuthorityType { + status: number; + message: string; + data: { + memberId: number; + tripAuthority: string; + TripId: number; + }; +} diff --git a/src/api/socket.ts b/src/api/socket.ts index 2be1d259..000830cf 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -102,8 +102,7 @@ export const pubUpdateTripItem = ( body: JSON.stringify(pubUpdateTripItem), }); - console.log(pubUpdateTripItem); - console.log('펍실행'); + console.log('데이터', pubUpdateTripItem); }; // 여행 날짜별 교통 수단 변경 이벤트 발생시 (01/16 업데이트) @@ -157,10 +156,9 @@ export const pubDisconnectMember = (pubMember: pubMember, tripId: string) => { }; // 여정 편집 페이지 입장 이벤트 발생시(모든 sub 다받음) -export const pubEnterMember = (pubMember: pubMember, tripId: string) => { +export const pubEnterMember = (tripId: string) => { socketClient.publish({ destination: `/pub/trips/${tripId}/enterMember`, - body: JSON.stringify(pubMember), }); }; diff --git a/src/api/trips.ts b/src/api/trips.ts index b9aac400..dc41f9e3 100644 --- a/src/api/trips.ts +++ b/src/api/trips.ts @@ -41,25 +41,15 @@ export const postTrips = async (tripsData: TripRequest) => { }; // 우리의 관심목록 조회 -export const getTripsLike = async (options: { - tripId: number; - category?: string; - page?: number; - size?: number; -}) => { - const { tripId, category, page = 0, size } = options; - - let query = `trips/${tripId}/tripLikedTours?`; - - if (category) { - query += `&category=${category}`; - } - query += `&page=${page}`; +export const getTripsLike = async ( + tripId: number, + page: number, + size: number, +) => { + const res = await authClient.get( + `trips/${tripId}/tripLikedTours?page=${page}&size=${size}`, + ); - if (size) { - query += `&size=${size}`; - } - const res = await authClient.get(query); return res.data; }; @@ -80,9 +70,10 @@ export const postTripsLikeHate = async ( tripId: number, tourId: number, prefer: boolean, + notPrefer: boolean, ) => { - const res = await client.post( - `trips/${tripId}/tripLikedTours/${tourId}?prefer=${prefer}`, + const res = await authClient.post( + `trips/${tripId}/tripLikedTours/${tourId}?prefer=${prefer}¬Prefer=${notPrefer}`, ); return res; }; @@ -102,3 +93,9 @@ export const getTripsMembers = async (tripId: number) => { const res = await client.get(`trips/${tripId}/members`); return res; }; + +// 편집권한 조회 +export const getTripsAuthority = async (tripId: string) => { + const res = await authClient.get(`trips/${tripId}/authority`); + return res; +}; diff --git a/src/components/DetailSectionTop/DetailTopButton.tsx b/src/components/DetailSectionTop/DetailTopButton.tsx index 225c4fc6..2e5cd923 100644 --- a/src/components/DetailSectionTop/DetailTopButton.tsx +++ b/src/components/DetailSectionTop/DetailTopButton.tsx @@ -1,51 +1,37 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; +import { reviewCountState } from '@recoil/review'; import { TopIcon } from '@components/common/icons/Icons'; -export default function DetailTopButton({ parentRef }: any) { - const [isVisible, setIsVisible] = useState(false); - const [scrollPosition, setScrollPosition] = useState(0); - const [viewportHeight, setViewportHeight] = useState(0); - - const scrollButtonRef = useRef(null); +export default function DetailTopButton() { + const [visible, setVisible] = useState(false); + const getReviewCount = useRecoilValue(reviewCountState); useEffect(() => { - const handleScroll = () => { - if (scrollButtonRef.current && parentRef.current) { - setViewportHeight(screen.height); - - // 부모 요소의 높이보다 적을 때까지 스크롤 허용 - if (window.scrollY < parentRef.current.clientHeight - 50) { - // 기기 높이의 절반 이상 스크롤 했을 때 - if (window.scrollY >= viewportHeight / 2) { - setIsVisible(true); - setScrollPosition(window.scrollY); - } else { - setIsVisible(false); - } - } - } - }; - - window.addEventListener('scroll', handleScroll); - - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, [parentRef, scrollPosition, setScrollPosition]); - - const scrollToTop = () => { + if (getReviewCount > 2) { + setVisible(true); + } else { + setVisible(false); + } + }, [getReviewCount]); + + const scrollToTop = (e: React.MouseEvent) => { + e.stopPropagation(); window.scrollTo({ top: 0, behavior: 'smooth' }); }; + if (!visible) { + return null; + } + return (
- + className="sticky bottom-5 + mt-[-50px] flex h-12 w-12 cursor-pointer items-center justify-center rounded-full bg-white shadow-md" + style={{ left: 'calc(100%)' }}> +
); } diff --git a/src/components/DetailSectionTop/DetailToursRating.tsx b/src/components/DetailSectionTop/DetailToursRating.tsx index 0178f248..2be49e9c 100644 --- a/src/components/DetailSectionTop/DetailToursRating.tsx +++ b/src/components/DetailSectionTop/DetailToursRating.tsx @@ -1,5 +1,8 @@ import { useEffect, useState } from 'react'; import { Link } from 'react-scroll'; +import { useSetRecoilState } from 'recoil'; +import { reviewCountState } from '@recoil/review'; + interface ReviewData { ratingAverage: number; reviewTotalCount: number; @@ -13,6 +16,7 @@ export default function DetailToursRating({ reviewData, }: DetailToursRatingProps) { const { reviewTotalCount, ratingAverage } = reviewData; + const setReviewCount = useSetRecoilState(reviewCountState); const STAR_IDX_ARR = ['1', '2', '3', '4', '5']; const [ratedStarArr, setRatedStarArr] = useState([0, 0, 0, 0, 0]); @@ -32,6 +36,7 @@ export default function DetailToursRating({ useEffect(() => { setRatedStarArr(calculateRates(ratingAverage)); + setReviewCount(reviewTotalCount); }, []); return ( diff --git a/src/components/MyTrip/MyTripItem.tsx b/src/components/MyTrip/MyTripItem.tsx index a91a0a2f..2762a3d1 100644 --- a/src/components/MyTrip/MyTripItem.tsx +++ b/src/components/MyTrip/MyTripItem.tsx @@ -59,7 +59,7 @@ const MyTripItem: React.FC = ({ myTripList }) => {
여행지 이미지 diff --git a/src/components/Plan/PlanEditItemBox.tsx b/src/components/Plan/PlanEditItemBox.tsx index ae5f4873..e5b0f944 100644 --- a/src/components/Plan/PlanEditItemBox.tsx +++ b/src/components/Plan/PlanEditItemBox.tsx @@ -59,8 +59,6 @@ const PlanEditItemBox = ({ visitDate: visitDate, tripItemOrder, }); - - console.log(newData); }; useEffect(() => { @@ -108,7 +106,7 @@ const PlanEditItemBox = ({ {(provided) => (
-
{day}
+
{day}
{items.map((item, index) => ( -
-
+
+
선택한 장소를 삭제하시겠습니까?} diff --git a/src/components/Plan/PlanItem.tsx b/src/components/Plan/PlanItem.tsx index c27cf354..a4342a68 100644 --- a/src/components/Plan/PlanItem.tsx +++ b/src/components/Plan/PlanItem.tsx @@ -6,11 +6,12 @@ import PlanItemBox from './PlanItemBox'; import PlanEditItemBox from './PlanEditItemBox'; import { useContext, useEffect, useState } from 'react'; import { socketContext } from '@hooks/useSocket'; -import { useRecoilState } from 'recoil'; +import { useRecoilState, useRecoilValue } from 'recoil'; import { visitDateState } from '@recoil/socket'; import { pubGetPathAndItems, pubUpdateTransportation } from '@api/socket'; import { tripIdState } from '@recoil/socket'; -import { useRecoilValue } from 'recoil'; +import { tapState } from '@recoil/plan'; +import { useGetTripsAuthority } from '@hooks/useGetTripsAuthority'; type PlanItemProps = { date: string; @@ -19,21 +20,22 @@ type PlanItemProps = { const PlanItem: React.FC = ({ date, day }) => { const navigate = useNavigate(); + const { tripAuthority } = useGetTripsAuthority(); const [isEdit, SetIsEdit] = useState(false); - const tripId = useRecoilValue(tripIdState); + const tap = useRecoilValue(tapState); const [visitDate, setVisitDate] = useRecoilState(visitDateState); const { tripItem, tripPath, callBackPub } = useContext(socketContext); + console.log(visitDate); useEffect(() => { - setVisitDate({ visitDate: date }); - }, [date]); - - useEffect(() => { - if (visitDate && tripId) { - callBackPub(() => pubGetPathAndItems(visitDate, tripId)); + if (tap) { + setVisitDate({ visitDate: date }); + if (date && tripId) { + callBackPub(() => pubGetPathAndItems({ visitDate: date }, tripId)); + } } - }, [visitDate]); + }, [tap]); const handleEdit = () => { SetIsEdit((prev) => !prev); @@ -41,14 +43,14 @@ const PlanItem: React.FC = ({ date, day }) => { const handleTranspo = ( transportation: 'CAR' | 'PUBLIC_TRANSPORTATION', - visitDate: string, + date: string, tripId: string, ) => { if (transportation !== transpo) { callBackPub(() => pubUpdateTransportation( { - visitDate: visitDate, + visitDate: date, transportation: transportation, }, tripId, @@ -64,14 +66,12 @@ const PlanItem: React.FC = ({ date, day }) => { {tripPath && }
- {isEdit ? ( + {tripAuthority !== 'WRITE' || isEdit ? (
) : ( -
+
- handleTranspo('CAR', visitDate?.visitDate || '', tripId || '') - } + onClick={() => handleTranspo('CAR', date || '', tripId || '')} className="flex h-[32px] w-[32px] cursor-pointer items-center justify-center rounded-l-md border border-solid border-gray3"> = ({ date, day }) => {
- handleTranspo( - 'PUBLIC_TRANSPORTATION', - visitDate?.visitDate || '', - tripId || '', - ) + handleTranspo('PUBLIC_TRANSPORTATION', date || '', tripId || '') } className="pointer-cursor -ml-[1px] flex h-[32px] w-[32px] cursor-pointer items-center justify-center rounded-r-md border border-solid border-gray3"> = ({ date, day }) => {
)} - - + {tripAuthority !== 'WRITE' ? ( + '' + ) : ( + + )}
@@ -110,7 +109,7 @@ const PlanItem: React.FC = ({ date, day }) => { ) : ( @@ -122,10 +121,11 @@ const PlanItem: React.FC = ({ date, day }) => { /> )}
-
- {isEdit ? ( - '' - ) : ( + + {tripAuthority !== 'WRITE' || isEdit ? ( +
+ ) : ( +
navigate('./place')} className="h-[40px] w-full"> @@ -134,8 +134,8 @@ const PlanItem: React.FC = ({ date, day }) => {
장소 추가하기
- )} -
+
+ )} ); }; diff --git a/src/components/Plan/PlanMoveItem.tsx b/src/components/Plan/PlanMoveItem.tsx index 9601061a..b06df513 100644 --- a/src/components/Plan/PlanMoveItem.tsx +++ b/src/components/Plan/PlanMoveItem.tsx @@ -94,9 +94,8 @@ const PlanMoveItem: React.FC = ({
{day.map((day, index) => ( - + } content={
- - +
+ setInputBudget(e.target.value)} + /> +
setInputBudget('')}> + {showCloseIcon && } +
+
+ +
} />
- {targetBudget.toLocaleString()} + {budget?.budget.toLocaleString() ?? '- '}
diff --git a/src/components/Plan/TripMap.tsx b/src/components/Plan/TripMap.tsx index 9816403e..7c167103 100644 --- a/src/components/Plan/TripMap.tsx +++ b/src/components/Plan/TripMap.tsx @@ -50,7 +50,6 @@ const TripMap = ({ paths }: { paths: Paths[] }) => { const MapStyle = { width: '100%', height: '180px', - marginTop: '15px', transition: 'height 0.3s ease-in-out', }; @@ -100,8 +99,6 @@ const TripMap = ({ paths }: { paths: Paths[] }) => { // 선택된 마커의 인덱스를 추적하기 위한 상태 const [selectedMarker, setSelectedMarker] = useState(null); - // ... - // 마커를 클릭할 때 호출되는 함수 const handleMarkerClick = (index: number) => { setSelectedMarker(index); @@ -127,22 +124,20 @@ const TripMap = ({ paths }: { paths: Paths[] }) => { svgComponent = isSelected ? FifthSelectedMarker : FifthMarker; break; default: - // 기본 마커가 필요한 경우 기본 마커 이미지 URL을 제공합니다. + // 기본 마커 return 'default_marker_image_url'; } return svgComponent; }; - // ... TripMap 컴포넌트 및 나머지 코드 - return ( -
+
{paths.map((path, index) => (
diff --git a/src/components/Review/CommentItem.tsx b/src/components/Review/CommentItem.tsx index c4e4d264..33712b0c 100644 --- a/src/components/Review/CommentItem.tsx +++ b/src/components/Review/CommentItem.tsx @@ -57,7 +57,7 @@ const CommentItem: React.FC = (props: ItemProps) => { return (
-
+
{!( authorProfileImageUrl === 'http://asiduheimage.jpg' || authorProfileImageUrl === null @@ -65,7 +65,7 @@ const CommentItem: React.FC = (props: ItemProps) => { 유저 프로필 ) : ( @@ -85,7 +85,7 @@ const CommentItem: React.FC = (props: ItemProps) => {
)}
-
{content}
+
{content}
); }; diff --git a/src/components/Trip/LikedToursList.tsx b/src/components/Trip/LikedToursList.tsx index 67119420..eab8eeea 100644 --- a/src/components/Trip/LikedToursList.tsx +++ b/src/components/Trip/LikedToursList.tsx @@ -1,3 +1,92 @@ +import { useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useInfiniteQuery } from '@tanstack/react-query'; + +import LikedToursListCategory from './LikedToursLists/LikedToursListCategory'; +import LikedToursListBox from './LikedToursLists/LikedToursListBox'; +import NoDataMessage from '@components/common/noData/NoDataMessage'; + +import { getTripsLike } from '@api/trips'; +import { HeartIcon, NewIcon } from '@components/common/icons/Icons'; + export const LikedToursList = () => { - return
LikedToursList
; + const [selectedContentTypeId, setSelectedContentTypeId] = useState< + null | number + >(null); + + const navigate = useNavigate(); + const params = useParams(); + + const [selectedTripId, _] = useState(Number(params.id)); + + const { fetchNextPage, hasNextPage, data, isLoading, error } = + useInfiniteQuery({ + queryKey: ['ourTrips'], + queryFn: ({ pageParam = 0 }) => + getTripsLike(selectedTripId, pageParam, 10), + initialPageParam: 0, + getNextPageParam: (lastPage) => { + if ( + lastPage && + lastPage.data && + lastPage.data && + lastPage.data.pageable + ) { + const currentPage = lastPage.data.pageable.pageNumber; + const totalPages = lastPage.data.totalPages; + + if (currentPage < totalPages - 1) { + return currentPage + 1; + } + } + return undefined; + }, + }); + + if (error) { + return
데이터를 불러오는 중 오류가 발생했습니다.
; + } + + const handleCategoryClick = (contentTypeId: number | null) => { + setSelectedContentTypeId(contentTypeId); + }; + + return ( +
+ + + {data?.pages[0].data.content.length > 0 ? ( +
+ +
+ ) : ( +
+ } + /> +
+ )} + + {/* 우리의 관심 여행지 추가 버튼 => 검색 라우터 이동 */} +
+ +
+
+ ); }; diff --git a/src/components/Trip/LikedToursLists/LikedToursListBox.tsx b/src/components/Trip/LikedToursLists/LikedToursListBox.tsx new file mode 100644 index 00000000..56c2db3d --- /dev/null +++ b/src/components/Trip/LikedToursLists/LikedToursListBox.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; +import { v4 as uuidv4 } from 'uuid'; +import ToursItemSkeleton from '@components/Tours/ToursItemSkeleton'; +// import { Spinner } from '@components/common/spinner/Spinner'; + +import LikedToursListItem from './LikedToursListItem'; + +interface LikedToursListProps { + toursData: { pages: Array<{ data: { content: ourTripType[] } }> }; + fetchNextPage: () => void; + hasNextPage: boolean; + isLoading: boolean; + selectedContentTypeId: number | null; + selectedTripId: number; +} + +const LikedToursListBox: React.FC = ({ + toursData, + fetchNextPage, + hasNextPage, + isLoading, + selectedContentTypeId, + selectedTripId, +}) => { + if (!toursData || toursData.pages.length === 0) { + return
데이터를 불러오는 중 오류가 발생했습니다.
; + } + + const filteredData = + selectedContentTypeId !== null + ? toursData.pages.map((group) => ({ + data: { + content: group.data.content.filter( + (item) => item.contentTypeId === selectedContentTypeId, + ), + }, + })) + : toursData.pages; + + return ( + fetchNextPage()} + hasMore={hasNextPage} + loader={ +
+ {/* */} +
+ }> +
+ {isLoading + ? Array.from({ length: 10 }, (_, index) => ( + + )) + : filteredData.map((group) => ( + + {group?.data.content.map((ourTripList: ourTripType) => ( + + ))} + + ))} +
+
+ ); +}; + +export default LikedToursListBox; diff --git a/src/components/Trip/LikedToursLists/LikedToursListCategory.tsx b/src/components/Trip/LikedToursLists/LikedToursListCategory.tsx new file mode 100644 index 00000000..8a243c2f --- /dev/null +++ b/src/components/Trip/LikedToursLists/LikedToursListCategory.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react'; +import { LikedToursListCategoryItem } from './LikedToursListCategoryItem'; + +interface LikedToursListCategoryProps { + onCategoryClick: (contentTypeId: number | null) => void; +} + +const LikedToursListCategory: React.FC = ({ + onCategoryClick, +}) => { + const [selectedCategory, setSelectedCategory] = useState('전체'); + + const categories = [ + { code: null, name: '전체' }, + { code: 12, name: '관광지' }, + { code: 32, name: '숙소' }, + { code: 39, name: '식당' }, + ]; + + const handleSelectCategory = (name: string) => { + setSelectedCategory(name); + window.scrollTo({ + top: 0, + left: 0, + behavior: 'smooth', + }); + }; + + return ( +
+ {categories.map((category) => { + return ( + + ); + })} +
+ ); +}; + +export default LikedToursListCategory; diff --git a/src/components/Trip/LikedToursLists/LikedToursListCategoryItem.tsx b/src/components/Trip/LikedToursLists/LikedToursListCategoryItem.tsx new file mode 100644 index 00000000..d795f8f0 --- /dev/null +++ b/src/components/Trip/LikedToursLists/LikedToursListCategoryItem.tsx @@ -0,0 +1,29 @@ +interface LikedToursListCategoryItemProps { + category: { code: number | null; name: string }; + onCategoryClick: (contentTypeId: number | null) => void; + onSelect: (name: string) => void; + isSelected: boolean; +} + +export const LikedToursListCategoryItem: React.FC< + LikedToursListCategoryItemProps +> = ({ category, onCategoryClick, onSelect, isSelected }) => { + const handleCategoryClick = () => { + if (category.code !== undefined) { + onCategoryClick(category.code); + onSelect(category.name); + } + }; + + const buttonStyle = isSelected + ? 'bg-[#28D8FF] text-white font-bold' + : 'bg-[#fff] text-[#888] border-[#ededed]'; + + return ( + + ); +}; diff --git a/src/components/Trip/LikedToursLists/LikedToursListItem.tsx b/src/components/Trip/LikedToursLists/LikedToursListItem.tsx new file mode 100644 index 00000000..8fb54656 --- /dev/null +++ b/src/components/Trip/LikedToursLists/LikedToursListItem.tsx @@ -0,0 +1,136 @@ +import { + ThumbsUp, + ThumbsDown, + ClickThumbsUp, + ClickThumbsDown, + StarIcon, +} from '@components/common/icons/Icons'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { postTripsLikeHate } from '@api/trips'; +import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; + +interface LikedToursListItemProps { + ourTripList: ourTripType; + selectedTripId: number; +} + +const LikedToursListItem: React.FC = ({ + ourTripList, + selectedTripId, +}) => { + const { + tourItemId, + ratingAverage, + reviewCount, + prefer, + notPrefer, + preferTotalCount, + notPreferTotalCount, + smallThumbnailUrl, + tourAddress, + title, + } = ourTripList; + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [thumbsState, setThumbsState] = useState({ + prefer: false, + notPrefer: false, + }); + + const { mutate: thumbsUpMutate } = useMutation({ + mutationFn: () => + postTripsLikeHate( + selectedTripId, + tourItemId, + thumbsState.prefer, + thumbsState.notPrefer, + ), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['ourTrips'] }); + }, + onError: () => console.log('error'), + }); + + const onClickThumbsUpButton = (e: React.MouseEvent) => { + e.stopPropagation(); + setThumbsState({ prefer: true, notPrefer: false }); + thumbsUpMutate(); + }; + + const onClickThumbsDownButton = (e: React.MouseEvent) => { + e.stopPropagation(); + setThumbsState({ prefer: false, notPrefer: true }); + thumbsUpMutate(); + }; + + return ( +
navigate(`/detail/${tourItemId}`)}> +
+
+ 여행지 이미지 +
+ +
+
+

+ {title} +

+ +
+
+
+ +
+ +
+ + {(Math.ceil(ratingAverage * 100) / 100).toFixed(1)} + + + ({reviewCount ? reviewCount.toLocaleString() : reviewCount}) + +
+
+ +
+

+ {tourAddress ? tourAddress : '주소를 제공하지 않고 있어요'} +

+
+
+
+ +
+
+ + +
+
+
+
+
+ ); +}; + +export default LikedToursListItem; diff --git a/src/components/Trip/PlanTripButton.tsx b/src/components/Trip/PlanTripButton.tsx index bd5ac6fc..34ce58a5 100644 --- a/src/components/Trip/PlanTripButton.tsx +++ b/src/components/Trip/PlanTripButton.tsx @@ -1,18 +1,25 @@ -import { PlanIcon, RightIcon } from '@components/common/icons/Icons'; +import { PlanColorIcon, RightIcon } from '@components/common/icons/Icons'; +import { useNavigate } from 'react-router-dom'; const PlanTripButton = () => { - return ( + const navigate = useNavigate(); + + const handleButtonClick = () => { + navigate('plan'); + }; - ); diff --git a/src/components/Trip/TripInfo.tsx b/src/components/Trip/TripInfo.tsx index 5594c89a..8dd648a5 100644 --- a/src/components/Trip/TripInfo.tsx +++ b/src/components/Trip/TripInfo.tsx @@ -1,4 +1,4 @@ -import { UserIcon } from '@components/common/icons/Icons'; +import { TripSchedule } from './TripSchedule'; import { useRecoilValue, useRecoilState } from 'recoil'; import { isModalOpenState, modalChildrenState } from '@recoil/modal'; import TripSurveyMember from '@components/common/modal/children/TripSurveyMember'; @@ -6,9 +6,10 @@ import { Modal } from '@components/common/modal'; import { useQuery } from '@tanstack/react-query'; import { getTripsMembers } from '@api/trips'; import { tripIdState } from '@recoil/socket'; -import { ReactComponent as NullUser } from '@assets/images/NullUser.svg'; +// import { ReactComponent as NullUser } from '@assets/images/NullUser.svg'; import { DownIcon } from '@components/common/icons/Icons'; import { useState } from 'react'; +import { UserIcon } from '@components/common/icons/Icons'; const ShareList = () => { const tripId = Number(useRecoilValue(tripIdState)); @@ -21,7 +22,7 @@ const ShareList = () => { return ( <>
-
+
{members.map((member: any, index: number) => { return (
{ className="h-[32px] w-[32px] rounded-full" /> ) : ( - +
+ +
+ // )}
{member.nickname}
@@ -81,7 +88,13 @@ const TripInfo = () => { className="h-[32px] w-[32px] rounded-full border-2 border-solid border-white" /> ) : ( - +
+ +
+ // )}
))} @@ -104,20 +117,7 @@ const TripInfo = () => {
{isAccordion && } -
-
-
-
강릉 여행 일정
-
- - 5 -
-
- -
- 23.12.23 - 23.12.25 +
{modalChildren === 'TripSurveyMember' && } diff --git a/src/components/Trip/TripParticipant.tsx b/src/components/Trip/TripParticipant.tsx index 934aa95e..d114a6d5 100644 --- a/src/components/Trip/TripParticipant.tsx +++ b/src/components/Trip/TripParticipant.tsx @@ -1,6 +1,7 @@ -import { ReactComponent as NullUser } from '@assets/images/NullUser.svg'; +// import { ReactComponent as NullUser } from '@assets/images/NullUser.svg'; import { useRecoilValue } from 'recoil'; import { participantsState } from '@recoil/trip'; +import { UserIcon } from '@components/common/icons/Icons'; interface ParticipantStatusProps { status: string; @@ -19,7 +20,13 @@ const ParticipantList: React.FC<{ infos: any[] }> = ({ infos }) => ( className="h-[32px] w-[32px] rounded-full" /> ) : ( - +
+ +
+ // )}
{info.nickname}
@@ -33,7 +40,7 @@ export const ParticipantStatus: React.FC = ({ const participants = useRecoilValue(participantsState); return ( -
+
{status == '참여' ? ( <>{participants?.tripSurveyMemberCount}명 참여 diff --git a/src/components/Trip/TripPreference.tsx b/src/components/Trip/TripPreference.tsx index 02f71976..ca11afda 100644 --- a/src/components/Trip/TripPreference.tsx +++ b/src/components/Trip/TripPreference.tsx @@ -32,7 +32,9 @@ const TripPreferenceButton: React.FC = () => {
-

내 여행 취향 설정하러 가기

+

+ 내 여행 취향 설정하러 가기 +

@@ -154,7 +156,7 @@ const TripPreference: React.FC = () => { }; return ( -
+
{ + const tripId = useRecoilValue(tripIdState); + const { callBackPub, tripMember } = useContext(socketContext); + const [token, setToken] = useState(''); + const [pubMember, setPubMember] = useState({ token: '' }); + + useEffect(() => { + const accessToken = getItem('accessToken'); + if (accessToken) { + setToken(accessToken); + } + }, []); + + useEffect(() => { + setPubMember({ token: token || '' }); + }, [token]); + + useEffect(() => { + if (pubMember && tripId) { + callBackPub(() => { + pubConnectMember(pubMember, tripId); + }); + return () => { + callBackPub(() => pubDisconnectMember(pubMember, tripId)); + }; + } + }, [pubMember]); + + const tripMemberData = tripMember?.data; + useEffect(() => { + console.log('tripMemberData', tripMemberData); + }, [tripMemberData]); + return ( +
+ + {tripMemberData?.tripMembers?.map((member) => { + const isConnected = member?.connected; + const thumbnailUrl = member?.thumbnailUrl; + const isImageUrlValid = + thumbnailUrl && thumbnailUrl !== 'http://asiduheimage.jpg'; + const imageUrl = isImageUrlValid ? thumbnailUrl : null; + + return ( + +
+ {imageUrl ? ( + 유저 프로필 + ) : ( +
+ +
+ )} +
+ + {member?.name} + +
+
+
+ ); + })} +
+
+ ); +}; + +export default TripRealtimeEditor; diff --git a/src/components/Trip/TripSchedule.tsx b/src/components/Trip/TripSchedule.tsx new file mode 100644 index 00000000..ad55e20e --- /dev/null +++ b/src/components/Trip/TripSchedule.tsx @@ -0,0 +1,22 @@ +import { UserIcon } from '@components/common/icons/Icons'; + +export const TripSchedule = () => { + return ( + <> +
+
+
+
강릉 여행 일정
+
+ + 5 +
+
+ +
+ 23.12.23 - 23.12.25 + + ); +}; diff --git a/src/components/Trip/TripSectionTop.tsx b/src/components/Trip/TripSectionTop.tsx index 82e03c8e..59ef58eb 100644 --- a/src/components/Trip/TripSectionTop.tsx +++ b/src/components/Trip/TripSectionTop.tsx @@ -5,9 +5,13 @@ import { BackBox } from '@components/common'; import { useNavigate } from 'react-router-dom'; import PlanTripButton from './PlanTripButton'; import { LikedToursList } from './LikedToursList'; +import { useGetTripsAuthority } from '@hooks/useGetTripsAuthority'; const TripSectionTop = () => { const navigate = useNavigate(); + const { tripAuthority } = useGetTripsAuthority(); + + console.log(tripAuthority); return (
diff --git a/src/components/Wish/WishItem.tsx b/src/components/Wish/WishItem.tsx index 6b30c0b5..e8c12eb9 100644 --- a/src/components/Wish/WishItem.tsx +++ b/src/components/Wish/WishItem.tsx @@ -38,12 +38,12 @@ const WishItem: React.FC = ({ wishList }) => {
-
-

+

+

{title}

-
-

{tourAddress}

+
+

{tourAddress}

diff --git a/src/components/common/icons/Icons.tsx b/src/components/common/icons/Icons.tsx index 34987b1f..af595fd3 100644 --- a/src/components/common/icons/Icons.tsx +++ b/src/components/common/icons/Icons.tsx @@ -1299,6 +1299,179 @@ export const CounterIcon: React.FC< ); }; +export const PlanColorIcon: React.FC = ({ size = 21 }) => { + return ( + + + + + + + ); +}; + +export const ThumbsUp: React.FC = ({ + size = 16, + fill = '#1E1E1E', +}) => { + return ( + + + + + + ); +}; + +export const ClickThumbsUp: React.FC = ({ + size = 16, + fill = '#1E1E1E', +}) => { + return ( + + + + + ); +}; + +export const ThumbsDown: React.FC = ({ + size = 16, + fill = '#1E1E1E', +}) => { + return ( + + + + + ); +}; + +export const ClickThumbsDown: React.FC = ({ + size = 16, + fill = '#1E1E1E', +}) => { + return ( + + + + + ); +}; + +export const NewIcon: React.FC = ({ + size = 11, + fill = '#1E1E1E', +}) => { + return ( + + + + + ); +}; + export const RedIcon: React.FC = ({ size = 20, className }) => { return ( { maxWidth: '412px', width: '100%', height: '186px', - borderTopLeftRadius: '2rem', - borderTopRightRadius: '2rem', + borderTopLeftRadius: '16px', + borderTopRightRadius: '16px', }, overlay: { backgroundColor: 'rgba(0, 0, 0, 0.25)', @@ -62,7 +62,7 @@ export const getModalStyles = (modalChildren: string) => { transform: 'translate(-50%, -50%)', width: '309px', height: '192px', - borderRadius: '2rem', + borderRadius: '16px', }, overlay: { backgroundColor: 'rgba(0, 0, 0, 0.25)', @@ -81,8 +81,8 @@ export const getModalStyles = (modalChildren: string) => { maxWidth: '412px', width: '100%', height: '280px', - borderTopLeftRadius: '2rem', - borderTopRightRadius: '2rem', + borderTopLeftRadius: '16px', + borderTopRightRadius: '16px', }, overlay: { backgroundColor: 'rgba(0, 0, 0, 0.25)', diff --git a/src/components/common/modal/children/MyAlert.tsx b/src/components/common/modal/children/MyAlert.tsx index e5f50f7e..7e226d68 100644 --- a/src/components/common/modal/children/MyAlert.tsx +++ b/src/components/common/modal/children/MyAlert.tsx @@ -69,7 +69,7 @@ const MyAlert: React.FC = ({ title, content }) => {
{title}
- {content.split('. ').map((sentence, index) => ( + {content.split(/(?<=\.) /).map((sentence, index) => (
{sentence}
diff --git a/src/components/common/tab/Tab.tsx b/src/components/common/tab/Tab.tsx index 3ae8f972..f3a799c9 100644 --- a/src/components/common/tab/Tab.tsx +++ b/src/components/common/tab/Tab.tsx @@ -1,37 +1,61 @@ import * as Tabs from '@radix-ui/react-tabs'; +import { useRecoilState } from 'recoil'; +import { tapState } from '@recoil/plan'; interface TabProps { lists: string[]; contents: React.ReactNode[]; } -const Tab = ({ lists, contents }: TabProps) => ( - - - {lists.map((list, index) => { +const Tab = ({ lists, contents }: TabProps) => { + const [, setTapState] = useRecoilState(tapState); + + const handleTabChange = (value: string) => { + const tabIndex = value.replace('tab', ''); + setTapState(tabIndex); + }; + + let isDayTab = false; + + lists.forEach((list) => { + if (list.includes('DAY')) { + isDayTab = true; + } + }); + + return ( + + + {lists.map((list, index) => { + return ( + + {list} + + ); + })} + + {contents.map((content, index) => { return ( - - {list} - + {content} + ); })} - - {contents.map((content, index) => { - return ( - - {content} - - ); - })} - -); + + ); +}; export default Tab; diff --git a/src/hooks/useGetTripsAuthority.ts b/src/hooks/useGetTripsAuthority.ts new file mode 100644 index 00000000..efef5695 --- /dev/null +++ b/src/hooks/useGetTripsAuthority.ts @@ -0,0 +1,39 @@ +import { useQuery } from '@tanstack/react-query'; +import { getTripsAuthority } from '@api/trips'; + +import { useParams } from 'react-router-dom'; + +type useGetTripsAuthorityReturn = { + tripAuthority: string | null; + memberId: number | null; + TripId: number | null; +}; + +export const useGetTripsAuthority = (): useGetTripsAuthorityReturn => { + const { id } = useParams(); + + const defaultReturn = { + tripAuthority: null, + memberId: null, + TripId: null, + }; + + if (!id) { + return defaultReturn; + } + const { data, isLoading, isError } = useQuery({ + queryKey: ['getTripsAuthority', id], + queryFn: () => getTripsAuthority(id), + enabled: !!id, + }); + + const tripAuthority = data?.data.data.tripAuthority; + const memberId = data?.data.data.memberId; + const TripId = data?.data.data.TripId; + + if (isLoading || isError) { + return defaultReturn; + } + + return { tripAuthority, memberId, TripId }; +}; diff --git a/src/hooks/useSocket.ts b/src/hooks/useSocket.ts index 50c9e2d6..6f07b0ca 100644 --- a/src/hooks/useSocket.ts +++ b/src/hooks/useSocket.ts @@ -63,6 +63,7 @@ export const useSocket = (tripId: string, visitDate: string) => { subMember(tripId, (res) => { if (res) { + // console.log('subMemberRes', res); setTripMember(res); } }); @@ -83,6 +84,7 @@ export const useSocket = (tripId: string, visitDate: string) => { useEffect(() => { socketConnect(); + console.log('소켓연결'); return () => { socketClient.deactivate(); diff --git a/src/pages/detail/detail.page.tsx b/src/pages/detail/detail.page.tsx index bcccedb5..cee1949a 100644 --- a/src/pages/detail/detail.page.tsx +++ b/src/pages/detail/detail.page.tsx @@ -2,20 +2,15 @@ import { DetailHeader } from '@components/common/header'; import DetailSectionTop from '@components/DetailSectionTop/DetailSectionTop'; import DetailSectionBottom from '@components/DetailSectionBottom/DetailSectionBottom'; import { DetailTopButton } from '@components/DetailSectionTop'; -import { useRef } from 'react'; const DetailTours = () => { - const parentRef = useRef(null); - return ( -
+ <> -
- - -
-
+ + + ); }; diff --git a/src/pages/plan/OurLikedList.tsx b/src/pages/plan/OurLikedList.tsx index 09e6fc3f..907790ac 100644 --- a/src/pages/plan/OurLikedList.tsx +++ b/src/pages/plan/OurLikedList.tsx @@ -19,15 +19,10 @@ export const OurLikedList = () => { const tripId = getTripIdFromUrl(); - const { fetchNextPage, hasNextPage, data, isLoading, isError } = + const { fetchNextPage, hasNextPage, data, isLoading, error } = useInfiniteQuery({ - queryKey: ['TripsLike'], - queryFn: ({ pageParam = 0 }) => - getTripsLike({ - tripId: tripId, - category: undefined, - page: pageParam, - }), + queryKey: ['ourTrips'], + queryFn: ({ pageParam = 0 }) => getTripsLike(tripId, pageParam, 10), initialPageParam: 0, getNextPageParam: (lastPage) => { if ( @@ -52,8 +47,8 @@ export const OurLikedList = () => { if (isLoading) { return ; } - if (isError) { - console.log('error fetching search result '); + if (error) { + return
데이터를 불러오는 중 오류가 발생했습니다.
; } const results = data?.pages.flatMap((page) => page.data.content) || []; diff --git a/src/pages/plan/addPlace/AddtoListBtn.tsx b/src/pages/plan/addPlace/AddtoListBtn.tsx index 0bcc5aa5..4b3bcb08 100644 --- a/src/pages/plan/addPlace/AddtoListBtn.tsx +++ b/src/pages/plan/addPlace/AddtoListBtn.tsx @@ -3,6 +3,7 @@ import { selectedItemsState } from '@recoil/listItem'; import { ButtonPrimary } from '@components/common/button/Button'; import { postTripsLike, putTrips } from '@api/trips'; import { getTripIdFromUrl } from '@utils/getTripIdFromUrl'; +import { useNavigate } from 'react-router-dom'; const AddToListButton = ({ apiType, @@ -12,6 +13,8 @@ const AddToListButton = ({ const selectedTourItemIds = useRecoilValue(selectedItemsState); const VISIT_DATE = '2024-02-01'; + const navigate = useNavigate(); + const handleAddClick = async () => { const tripId = getTripIdFromUrl(); if (tripId) { @@ -19,6 +22,7 @@ const AddToListButton = ({ let response; if (apiType === 'postTripsLike') { response = await postTripsLike(tripId, selectedTourItemIds); + navigate(`/trip/${tripId}`); } else if (apiType === 'putTrips' && selectedTourItemIds.length > 0) { response = await putTrips(tripId, selectedTourItemIds[0], VISIT_DATE); } diff --git a/src/recoil/plan.ts b/src/recoil/plan.ts index c938efaa..42e69971 100644 --- a/src/recoil/plan.ts +++ b/src/recoil/plan.ts @@ -9,3 +9,8 @@ export const dateState = atom({ key: 'dateState', default: [''], }); + +export const tapState = atom({ + key: 'tapState', + default: '', +}); diff --git a/src/recoil/review.ts b/src/recoil/review.ts index 6bb84cc2..1c9753e1 100644 --- a/src/recoil/review.ts +++ b/src/recoil/review.ts @@ -88,3 +88,8 @@ export const inputFocusState = atom({ key: 'inputFocusState', default: false, }); + +export const reviewCountState = atom({ + key: 'reviewCountState', + default: 0, +}); diff --git a/src/recoil/socket.ts b/src/recoil/socket.ts index d722c733..ab7bb76b 100644 --- a/src/recoil/socket.ts +++ b/src/recoil/socket.ts @@ -10,7 +10,7 @@ export const visitDateState = atom<{ visitDate: string } | null>({ default: { visitDate: '2024-01-03' }, }); -export const memberIdState = atom<{ memberId: number } | null>({ +export const memberIdState = atom<{ token: number | null }>({ key: 'memberIdState', - default: { memberId: 1 }, + default: { token: null }, }); diff --git a/src/router/socketRouter.tsx b/src/router/socketRouter.tsx index a48c16dc..e679617b 100644 --- a/src/router/socketRouter.tsx +++ b/src/router/socketRouter.tsx @@ -19,9 +19,9 @@ const SocketRoutes = () => { return ( - } /> - } /> - } /> + } /> + } /> + } /> ); @@ -33,7 +33,7 @@ const SocketRouter = () => { }> } /> } /> - } /> + } /> );