Skip to content

Commit

Permalink
✨ Feat : 메인페이지 맞춤형 추천 기능 구현
Browse files Browse the repository at this point in the history
✨ Feat : 메인페이지 맞춤형 추천 기능 구현
  • Loading branch information
eunjju2 authored Dec 7, 2024
2 parents 22d0397 + daa8b54 commit 3ed3e70
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 29 deletions.
8 changes: 8 additions & 0 deletions src/apis/workplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ export const postPositionWorkPlace = async ({
return response.data;
};

// 하이브리드 필터링 사업장 조회
export const getRecommendWorkPlace = async (): Promise<
GetPositionWorkPlaceData[]
> => {
const response = await authInstance.post('/api/v1/recommend');
return response.data;
};

// 사업자 사업장 조회
export const getBusinessWorkPlace =
async (): Promise<GetBusinessWorkPlaceData> => {
Expand Down
24 changes: 22 additions & 2 deletions src/pages/MainPage/components/KakaoMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import PlaceModal from './PlaceModal';

interface KakaoMapProps {
data: GetPositionWorkPlaceData[] | undefined;
activeTab: string;
recommendData: GetPositionWorkPlaceData[];
}

const KakaoMap = (props: KakaoMapProps) => {
const { data } = props;
const { data, activeTab, recommendData } = props;

const {
mapPosition,
Expand Down Expand Up @@ -88,7 +90,8 @@ const KakaoMap = (props: KakaoMapProps) => {
setCenterPosition({ lat: latlng.getLat(), lng: latlng.getLng() });
}}
>
{data &&
{activeTab === '주변 스터디룸' &&
data &&
data.length > 0 &&
data.map((item) => (
<MapMarker
Expand All @@ -104,6 +107,23 @@ const KakaoMap = (props: KakaoMapProps) => {
}}
/>
))}
{activeTab === '맞춤형 추천' &&
recommendData &&
recommendData.length > 0 &&
recommendData.map((item) => (
<MapMarker
onClick={() => handleMarkerClick(item)}
key={item.positionLat}
position={{ lat: item.positionLat, lng: item.positionLon }}
image={{
src: 'https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/markerStar.png',
size: {
width: 24,
height: 35,
},
}}
/>
))}
<div className='absolute right-0 top-0 z-10 m-4'>
<button
type='button'
Expand Down
88 changes: 72 additions & 16 deletions src/pages/MainPage/components/MainList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { GetPositionWorkPlaceData } from '@typings/types';
import { useNavigate } from 'react-router-dom';
import StudyRoomCard from './StudyRoomCard';

type TabList = {
Expand All @@ -10,24 +11,54 @@ type TabList = {
};

interface MainListProps {
activeTab: string;
OnSetActiveTab: (value: string) => void;
data: GetPositionWorkPlaceData[] | undefined;
isLoading: boolean;
isError: boolean;
recommendData: GetPositionWorkPlaceData[];
isRecommendLoading: boolean;
isRecommendError: boolean;
isLogin: boolean;
isUser: boolean;
}

const MainList = (props: MainListProps) => {
const { data, isLoading, isError } = props;
const [activeTab, setActiveTab] = useState('주변 스터디룸');
const tabList: TabList = {
const {
activeTab,
OnSetActiveTab,
data,
isLoading,
isError,
recommendData,
isRecommendLoading,
isRecommendError,
isLogin,
isUser,
} = props;
const [tabList, setTabList] = useState<TabList>({
'주변 스터디룸': {
title: '내 주변 스터디룸',
context: '내 주변 가까운 스터디룸을 확인해보세요 !',
},
'맞춤형 추천': {
title: '맞춤형 추천 스터디룸',
context: '나에게 맞는 스터디룸을 확인해보세요 !',
},
};
});

const navigate = useNavigate();

useEffect(() => {
if ((!isLogin && !isUser) || isUser) {
setTabList({
'주변 스터디룸': {
title: '내 주변 스터디룸',
context: '내 주변 가까운 스터디룸을 확인해보세요 !',
},
'맞춤형 추천': {
title: '맞춤형 추천 스터디룸',
context: '나에게 맞는 스터디룸을 확인해보세요 !',
},
});
}
}, [isLogin, isUser]);

return (
<div className='relative z-10 -mt-2 mb-[94px] min-h-[429px] w-[375px] rounded-t-[10px] bg-white pb-[110px] shadow-custom'>
Expand All @@ -36,8 +67,8 @@ const MainList = (props: MainListProps) => {
<button
type='button'
key={tab}
className={`h-full w-1/2 text-base ${activeTab === tab ? 'border-b-2 border-b-primary text-black' : 'text-subfont'}`}
onClick={() => setActiveTab(tab)}
className={`h-full flex-1 text-base ${activeTab === tab ? 'border-b-2 border-b-primary text-black' : 'text-subfont'}`}
onClick={() => OnSetActiveTab(tab)}
>
{tab}
</button>
Expand All @@ -58,18 +89,43 @@ const MainList = (props: MainListProps) => {
studyroom={item}
/>
))}
{activeTab === '맞춤형 추천' && !isLogin && (
<div className='flex h-[150px] w-full flex-col items-center justify-center gap-2 text-sm font-normal text-subfont'>
로그인 후 이용이 가능합니다.
<button
type='button'
className='flex h-[40px] w-[100px] items-center justify-center rounded-lg border border-primary py-2 text-primary'
onClick={() => navigate('/start')}
>
로그인
</button>
</div>
)}
{activeTab === '맞춤형 추천' &&
!isRecommendLoading &&
recommendData &&
recommendData.length > 0 &&
recommendData.map((item) => (
<StudyRoomCard
key={item.workplaceName}
studyroom={item}
/>
))}
{(!isLoading || isError) &&
activeTab === '주변 스터디룸' &&
(!data || data.length === 0) && (
<div className='flex h-[150px] w-full items-center justify-center text-[14px] font-normal text-subfont'>
주변 스터디룸이 없습니다.
</div>
)}
{activeTab === '맞춤형 추천' && (
<div className='flex h-[150px] w-full items-center justify-center text-[14px] font-normal text-subfont'>
추천 스터디룸이 없습니다.
</div>
)}
{(!isRecommendLoading || isRecommendError) &&
(!recommendData || recommendData.length === 0) &&
isLogin &&
activeTab === '맞춤형 추천' && (
<div className='flex h-[150px] w-full items-center justify-center text-[14px] font-normal text-subfont'>
추천 스터디룸이 없습니다.
</div>
)}
</div>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion src/pages/MainPage/components/StudyRoomCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ const StudyRoomCard = ({ studyroom }: StudyRoomCardProps) => {
<div className='flex-col gap-1'>
<p className='truncate text-start font-normal'>{workplaceName}</p>
<div className='flex gap-[10px]'>
<span className='text-xs font-normal'>{distance.toFixed(2)}km</span>
{distance && (
<span className='text-xs font-normal'>
{distance.toFixed(2)}km
</span>
)}
<span className='text-start text-xs'>{formattedAddress}</span>
</div>
</div>
Expand Down
18 changes: 15 additions & 3 deletions src/pages/MainPage/hooks/useGetWorkplaceData.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { postPositionWorkPlace } from '@apis/workplace';
import { getRecommendWorkPlace, postPositionWorkPlace } from '@apis/workplace';
import { useQuery } from '@tanstack/react-query';
import {
GetPositionWorkPlaceData,
MapPosition,
NowPosition,
} from '@typings/types';

const useGetWorkplaceData = (
export const useGetWorkplaceData = (
nowPosition: NowPosition,
mapPosition: MapPosition,
) => {
Expand All @@ -23,4 +23,16 @@ const useGetWorkplaceData = (
return { data, isLoading, isError, refetch };
};

export default useGetWorkplaceData;
export const useGetRecommendData = (isLogin: boolean, isUser: boolean) => {
const { data, isLoading, isError } = useQuery<GetPositionWorkPlaceData[]>({
queryKey: ['recommendWorkPlace', isLogin, isUser],
queryFn: () => getRecommendWorkPlace(),
enabled: isLogin && isUser,
});

return {
data: (data ?? []) as GetPositionWorkPlaceData[],
isLoading,
isError,
};
};
47 changes: 41 additions & 6 deletions src/pages/MainPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import MainLayout from '@layouts/MainLayout';
import HeaderNoTitle from '@layouts/HeaderNoTitle';
import BottomNavigation from '@layouts/BottomNavigation';
import { useEffect } from 'react';
import usePositionStore from '@store/positionStore';
import useAuthStore from '@store/authStore';
import { useEffect, useState } from 'react';
import { getRole } from '@utils/auth';
import MainList from './components/MainList';
import KakaoMap from './components/KakaoMap';
import useGetWorkplaceData from './hooks/useGetWorkplaceData';
import {
useGetWorkplaceData,
useGetRecommendData,
} from './hooks/useGetWorkplaceData';

const MainPage = () => {
const { mapPosition, nowPosition } = usePositionStore();
Expand All @@ -15,25 +20,55 @@ const MainPage = () => {
longitude: nowPosition.center.lng,
};

// 위치별 조회 데이터
const { data, isLoading, isError } = useGetWorkplaceData(
position,
mapPosition,
);

// 스크롤 상단으로 이동
// 비로그인 / 사업자 / 사용자 확인
const { isLogin } = useAuthStore();
const [isUser, setIsUser] = useState<boolean>(false);

useEffect(() => {
window.scrollTo(0, 0);
}, []);
if (isLogin) {
const role = getRole();
if (role === 'ROLE_USER') {
setIsUser(true);
}
}
}, [isLogin]);

// 사업장 추천 데이터
const {
data: recommendData,
isLoading: isRecommendLoading,
isError: isRecommendError,
} = useGetRecommendData(isLogin, isUser);

// 선택한 탭
const [activeTab, setActiveTab] = useState('주변 스터디룸');

return (
<>
<MainLayout>
<HeaderNoTitle />
<KakaoMap data={data} />
<KakaoMap
data={data}
activeTab={activeTab}
recommendData={recommendData}
/>
<MainList
activeTab={activeTab}
OnSetActiveTab={setActiveTab}
data={data}
isLoading={isLoading}
isError={isError}
recommendData={recommendData}
isRecommendLoading={isRecommendLoading}
isRecommendError={isRecommendError}
isLogin={isLogin}
isUser={isUser}
/>
<BottomNavigation />
</MainLayout>
Expand Down
2 changes: 1 addition & 1 deletion src/typings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export interface GetPositionWorkPlaceData {
reviewCount: number;
positionLat: number; //
positionLon: number;
distance: number;
distance?: number;
}

// 특정 사업자의 사업장
Expand Down

0 comments on commit 3ed3e70

Please sign in to comment.