Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 레스토랑 카드 및 마커 클릭 이벤트 변경 #198

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
462bbb7
feat: 마커 호버시 마커를 맨 앞으로 가져오기 (#192)
shackstack Jul 27, 2023
161d6fe
style: 마커 호버시 마커 강조 (#192)
shackstack Jul 27, 2023
0838f20
feat: 마커 클릭시 레스토랑 카드 띄우기 (#192)
shackstack Jul 27, 2023
d09af48
refactor: 불필요한 코드 제거 (#192)
shackstack Jul 27, 2023
11cf5f8
feat: 마커클릭 시 마커 위치에 따라 카드모달 위치 조정 (#192)
shackstack Jul 27, 2023
8748b91
style: 레스토랑 오버레이 스타일 수정 (#192)
shackstack Jul 28, 2023
11ba021
refactor: restaurantCard 컴포넌트를 용도에 따라 스타일 다르게 설정 (#192)
shackstack Jul 28, 2023
43ca047
feat: 마커 클릭시 강조 효과 주기 (#192)
shackstack Jul 28, 2023
fdeb97a
Squashed commit of the following:
shackstack Jul 28, 2023
f731c1d
feat: 다른 마커 클릭시 기존 마커 모달 닫기 기능 구현 (#192)
shackstack Jul 28, 2023
b4ad157
Merge branch 'develop-frontend' into 192-feat-레스토랑-카드-및-마커-클릭-이벤트-변경
shackstack Jul 28, 2023
d9bd80d
style: 파일명 오류 수정 (#192)
shackstack Jul 29, 2023
dbb297f
refactor: baseURL 환경변수 설정 및 type import 분리
shackstack Jul 29, 2023
a6ae8ef
feat: RestaurantCard 컴포넌트 props 수정 (#192)
shackstack Jul 29, 2023
5b69f96
refactor: getQuadrant 리팩터링 (#192)
shackstack Jul 29, 2023
3df8276
style: 상태 네이밍 수정 (#192)
shackstack Jul 30, 2023
c3a5c15
Merge branch 'shackstack' into 192-feat-레스토랑-카드-및-마커-클릭-이벤트-변경
shackstack Jul 30, 2023
e8e9e31
feat: 음식점 카드 호버시 해당 음식점 마커 강조 (#192)
shackstack Jul 30, 2023
ab9e38d
design: 강조시 애니메이션 효과 추가 및 음식점 리스트 스타일 수정 (#192)
shackstack Jul 31, 2023
9141668
refactor: 음식점 카드 호버시 마커 강조 로직 변경 (#192)
shackstack Jul 31, 2023
34b12a6
refactor: 프로필 이미지 컴포넌트 Props 타입 수정 (#192)
shackstack Jul 31, 2023
750fe7f
fix: setHoverId가 없을 때 default value 설정 (#192)
shackstack Jul 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions frontend/src/components/@common/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { styled } from 'styled-components';
import OverlayMarker from './OverlayMarker';
import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { Celeb } from '~/@types/celeb.types';
import MapContent from './MapContent';
import OverlayMyLocation from './OverlayMyLocation';
import LoadingDots from '../LoadingDots';
Expand All @@ -13,10 +12,11 @@
import RightBracket from '~/assets/icons/right-bracket.svg';
import Minus from '~/assets/icons/minus.svg';
import Plus from '~/assets/icons/plus.svg';
import { RestaurantData } from '~/@types/api.types';
shackstack marked this conversation as resolved.
Show resolved Hide resolved
import getQuadrant from '~/utils/getQuadrant';

interface MapProps {
clickMarker: ({ lat, lng }: Coordinate) => void;
markers: { position: Coordinate; celebs: Celeb[] }[];
data: RestaurantData[];
setBoundary: React.Dispatch<React.SetStateAction<CoordinateBoundary>>;
toggleMapExpand: () => void;
}
Expand All @@ -27,20 +27,22 @@
return <LoadingDots />;
};

function Map({ clickMarker, markers, setBoundary, toggleMapExpand }: MapProps) {
function Map({ data, setBoundary, toggleMapExpand }: MapProps) {
const [center, setCenter] = useState<Coordinate>({ lat: 37.5057482, lng: 127.050727 });
const [clicks, setClicks] = useState<google.maps.LatLng[]>([]);
const [zoom, setZoom] = useState(16);
const [myPosition, setMyPosition] = useState<Coordinate | null>(null);
const [isMapExpanded, setIsMapExpanded] = useState(false);
const [loading, setLoading] = useState(false);
const [mainPosition, setMainPosition] = useState({ lat: 37.5057482, lng: 127.050727 });
shackstack marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 center 상태로는 이 mainPosition 의 상태를 대체하기는 힘들까요? (제가 개인적으로 mainPosition을 center로 바꾸어 보았는데 실패하긴 했습니다.🥹)
무언가.. 중복되는 상태가 두개 있는 느낌이라서 하나로 만들고픈 욕구가 샘솟네요..
그리고! 타입을 좁혀주면 더 좋을 것 같아요!

Suggested change
const [mainPosition, setMainPosition] = useState({ lat: 37.5057482, lng: 127.050727 });
const [mainPosition, setMainPosition] = useState<Coordinate>({ lat: 37.5057482, lng: 127.050727 });


const onClick = (e: google.maps.MapMouseEvent) => {
setClicks([...clicks, e.latLng!]);

Check warning on line 40 in frontend/src/components/@common/Map/Map.tsx

View workflow job for this annotation

GitHub Actions / 🍔테스트 딱 대라 💢👊

Forbidden non-null assertion
};

const onIdle = (m: google.maps.Map) => {
setZoom(m.getZoom()!);

Check warning on line 44 in frontend/src/components/@common/Map/Map.tsx

View workflow job for this annotation

GitHub Actions / 🍔테스트 딱 대라 💢👊

Forbidden non-null assertion
setMainPosition({ lat: m.getCenter().lat(), lng: m.getCenter().lng() });

const lowLatitude = String(m.getBounds().getSouthWest().lat());
const highLatitude = String(m.getBounds().getNorthEast().lat());
Expand All @@ -60,11 +62,6 @@
});
};

const clickOverlayMarker = (position: Coordinate) => {
clickMarker(position);
setCenter(position);
};

const clickZoom =
(number: number): React.MouseEventHandler<HTMLButtonElement> =>
() => {
Expand All @@ -85,9 +82,16 @@
zoom={zoom}
center={center}
>
{markers.map(({ position, celebs }) => (
<OverlayMarker position={position} onClick={clickOverlayMarker} celeb={celebs[0]} />
))}
{data.map(({ celebs, ...restaurant }) => {
const { lat, lng } = restaurant;
return (
<OverlayMarker
restaurant={restaurant}
celeb={celebs[0]}
quadrant={getQuadrant(mainPosition, { lat, lng })}
/>
);
})}
{myPosition && <OverlayMyLocation position={myPosition} />}
{loading && (
<LoadingUI>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/@common/Map/Overlay/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ function Overlay({ position, pane = 'floatPane', map, zIndex, children }: Overla
const container = useMemo(() => {
const div = document.createElement('div');
div.style.position = 'absolute';

return div;
}, []);

Expand Down
71 changes: 58 additions & 13 deletions frontend/src/components/@common/Map/OverlayMarker.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,82 @@
import styled from 'styled-components';
import styled, { keyframes } from 'styled-components';
import { useRef, useState } from 'react';
import ProfileImage from '../ProfileImage';
import Overlay from './Overlay/Overlay';
import type { Celeb } from '~/@types/celeb.types';
import type { Coordinate } from '~/@types/map.types';
import { Restaurant } from '~/@types/restaurant.types';
import RestaurantCard from '~/components/RestaurantCard';
shackstack marked this conversation as resolved.
Show resolved Hide resolved
import type { Quadrant } from '~/utils/getQuadrant';
import useOnClickOutside from '~/hooks/useOnClickOuside';

interface OverlayMarkerProps {
celeb: Celeb;
position: Coordinate;
onClick: ({ lat, lng }: Coordinate) => void;
map?: google.maps.Map;
restaurant: Restaurant;
quadrant: Quadrant;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quadrant가 사분면을 뜻하는 군요 ㅋㅋㅋㅋ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다ㅋㅋ 저도 이번에 구현하면서 처음 알게된 단어입니다ㅋㅋ


function OverlayMarker({ celeb, position, map, onClick }: OverlayMarkerProps) {
function OverlayMarker({ celeb, restaurant, map, quadrant }: OverlayMarkerProps) {
const { lat, lng } = restaurant;
const [isClicked, setIsClicked] = useState(false);
const ref = useRef();
useOnClickOutside(ref, () => setIsClicked(false));

return (
map && (
<Overlay position={position} map={map}>
<StyledMarker type="button" onClick={() => onClick(position)}>
<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} size={32} border />
<Overlay position={{ lat, lng }} map={map} zIndex={isClicked ? 18 : 0}>
<StyledMarker onClick={() => setIsClicked(true)} isClicked={isClicked} ref={ref}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

슬슬... zIndex단이 많이 나오는 것 같네요..!

지금 할일이 아닐 수 있겠지만, 나중에라도 다같이 zIndex 단을 따로 정해보는게 좋을 것 같아요!

<ProfileImage name={celeb.name} imageUrl={celeb.profileImageUrl} border />
shackstack marked this conversation as resolved.
Show resolved Hide resolved
</StyledMarker>
{isClicked && (
<StyledModal quadrant={quadrant}>
<RestaurantCard restaurant={restaurant} onClick={() => {}} type="map" />
</StyledModal>
shackstack marked this conversation as resolved.
Show resolved Hide resolved
)}
</Overlay>
)
);
}

const StyledMarker = styled.button`
border: none;
background-color: transparent;
const StyledMarker = styled.div<{ isClicked: boolean }>`
display: flex;
justify-content: center;
align-items: center;

width: 36px;
height: 36px;

border: ${({ isClicked }) => (isClicked ? '3px solid var(--orange-2)' : '3px solid transparent')};
border-radius: 50%;

transition: all 0.2s ease-in-out;
transition: transform 0.2s ease-in-out;
transform: ${({ isClicked }) => (isClicked ? 'scale(1.5)' : 'scale(1)')};

&:hover {
transform: scale(1.1);
transform: scale(1.5);
}
`;

const fadeInAnimation = keyframes`
from {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 애니메이션은 많이 쓰일거 같네용 ~~ 공통 css로 빼도 좋을 거 같아여!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 컴포넌트에서 위와 같은 애니메이션을 쓰게되면 그때 분리해도 늦진 않을 것 같아요.

opacity: 0;
}
to {
opacity: 1;
}
`;

const StyledModal = styled.div<{ quadrant: Quadrant }>`
position: absolute;
top: ${({ quadrant }) => (quadrant === 1 || quadrant === 2 ? '40px' : '-280px')};
right: ${({ quadrant }) => (quadrant === 1 || quadrant === 4 ? '45px' : '-210px')};

width: 200px;

border-radius: 12px;
background-color: #fff;

animation: ${fadeInAnimation} 100ms ease-in;
box-shadow: 0 4px 6px rgb(0 0 0 / 20%);
`;

export default OverlayMarker;
7 changes: 3 additions & 4 deletions frontend/src/components/@common/ProfileImage/ProfileImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface ProfileImageProps extends React.HTMLAttributes<HTMLImageElement> {
name: string;
imageUrl: string;
border?: boolean;
size: number;
size?: number;
}

function ProfileImage({ name = '셀럽', imageUrl, size, border = false, ...props }: ProfileImageProps) {
Expand All @@ -14,10 +14,9 @@ function ProfileImage({ name = '셀럽', imageUrl, size, border = false, ...prop
export default ProfileImage;

const StyledProfile = styled.img<{ size: number; border: boolean }>`
width: ${({ size }) => `${size}px`};
height: ${({ size }) => `${size}px`};
width: ${({ size }) => (size ? `${size}px` : '100%')};
shackstack marked this conversation as resolved.
Show resolved Hide resolved
height: ${({ size }) => (size ? `${size}px` : 'auto')};

border: ${({ border }) => (border ? `2px solid var(--primary-1)` : `none`)};
border-radius: 50%;
background: none;
`;
20 changes: 13 additions & 7 deletions frontend/src/components/RestaurantCard/RestaurantCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ import { Celeb } from '~/@types/celeb.types';

interface RestaurantCardProps {
restaurant: Restaurant;
celebs: Celeb[];
size: number;
celebs?: Celeb[];
size?: number;
type?: 'list' | 'map';
onClick: React.MouseEventHandler;
}

function RestaurantCard({ restaurant, celebs, size, onClick }: RestaurantCardProps) {
function RestaurantCard({ restaurant, celebs, size, type = 'list', onClick }: RestaurantCardProps) {
const { images, name, roadAddress, category } = restaurant;

return (
<StyledContainer onClick={onClick}>
<StyledImage alt={`${name} 대표 이미지`} src={`http://3.35.157.27:3000/images-data/${images[0].name}`} />
<StyledImage
alt={`${name} 대표 이미지`}
src={`http://3.35.157.27:3000/images-data/${images[0].name}`}
shackstack marked this conversation as resolved.
Show resolved Hide resolved
type={type}
/>
<section>
<StyledInfo>
<StyledCategory>{category}</StyledCategory>
Expand All @@ -25,7 +30,7 @@ function RestaurantCard({ restaurant, celebs, size, onClick }: RestaurantCardPro
<StyledAddress>02-1234-5678</StyledAddress>
</StyledInfo>
<StyledProfileImageSection>
<ProfileImage name={celebs[0].name} imageUrl={celebs[0].profileImageUrl} size={size} />
{celebs && <ProfileImage name={celebs[0].name} imageUrl={celebs[0].profileImageUrl} size={size} />}
</StyledProfileImageSection>
</section>
</StyledContainer>
Expand All @@ -51,11 +56,12 @@ const StyledContainer = styled.div`
cursor: pointer;
`;

const StyledImage = styled.img`
const StyledImage = styled.img<{ type: 'list' | 'map' }>`
width: 100%;
aspect-ratio: 1.05 / 1;

border-radius: ${BORDER_RADIUS.md};
border-radius: ${({ type }) =>
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0`};

object-fit: cover;
`;
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/hooks/useOnClickOuside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, RefObject } from 'react';
shackstack marked this conversation as resolved.
Show resolved Hide resolved

export default function useOnClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject<T>,
handler: (event?: Event | MouseEvent) => void,
) {
useEffect(() => {
function onClickHandler(event: Event | MouseEvent) {
if (!ref?.current || ref?.current.contains(event?.target as Node)) {
return;
}
handler(event);
}
window.addEventListener('click', onClickHandler);
return () => {
window.removeEventListener('click', onClickHandler);
};
}, [ref, handler]);
}
43 changes: 5 additions & 38 deletions frontend/src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,18 @@ import Header from '~/components/@common/Header';
import Map from '~/components/@common/Map';
import CategoryNavbar from '~/components/CategoryNavbar';
import CelebDropDown from '~/components/CelebDropDown/CelebDropDown';
import MapModal from '~/components/MapModal/MapModal';
import RestaurantCard from '~/components/RestaurantCard';
import RESTAURANT_CATEGORY from '~/constants/restaurantCategory';
import { CELEBS_OPTIONS } from '~/constants/celebs';
import useFetch from '~/hooks/useFetch';
import useMapModal from '~/hooks/useMapModal';
import getQueryString from '~/utils/getQueryString';
import { FONT_SIZE } from '~/styles/common';
import type { Celeb } from '~/@types/celeb.types';
import type { RestaurantData } from '~/@types/api.types';
import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { Restaurant, RestaurantCategory, RestaurantModalInfo } from '~/@types/restaurant.types';
import type { CoordinateBoundary } from '~/@types/map.types';
import type { RestaurantCategory } from '~/@types/restaurant.types';

function MainPage() {
const [currentRestaurant, setCurrentRestaurant] = useState<RestaurantModalInfo | null>(null);
const { modalOpen, isVisible, closeModal, openModal } = useMapModal(true);
const [isMapExpanded, setIsMapExpanded] = useState(false);
const [data, setData] = useState<RestaurantData[]>([]);
const [boundary, setBoundary] = useState<CoordinateBoundary>();
Expand All @@ -32,28 +28,12 @@ function MainPage() {
async (queryObject: { boundary: CoordinateBoundary; celebId: number; category: RestaurantCategory }) => {
const queryString = getQueryString(queryObject);
const response = await handleFetch({ queryString });

setData(response.content);
},
[boundary, celebId, restaurantCategory],
);

const clickCard = (restaurant: Restaurant) => {
const { lat, lng, ...restaurantModalInfo } = restaurant;

openModal();
setCurrentRestaurant(restaurantModalInfo);
};

const clickMarker = ({ lat, lng }: Coordinate) => {
const filteredRestaurant = data.find(restaurantData => lat === restaurantData.lat && lng === restaurantData.lng);

const { id, name, category, roadAddress, phoneNumber, naverMapUrl, images }: RestaurantModalInfo =
filteredRestaurant;

setCurrentRestaurant({ id, name, category, roadAddress, phoneNumber, naverMapUrl, images });
};

const clickRestaurantCategory = (e: React.MouseEvent<HTMLElement>) => {
const currentCategory = e.currentTarget.dataset.label as RestaurantCategory;

Expand Down Expand Up @@ -89,25 +69,12 @@ function MainPage() {
<StyledCardListHeader>음식점 수 {data.length} 개</StyledCardListHeader>
<StyledRestaurantCardList>
{data?.map(({ celebs, ...restaurant }: RestaurantData) => (
<RestaurantCard restaurant={restaurant} celebs={celebs} size={42} onClick={() => clickCard(restaurant)} />
<RestaurantCard restaurant={restaurant} celebs={celebs} size={42} onClick={() => {}} />
shackstack marked this conversation as resolved.
Show resolved Hide resolved
))}
</StyledRestaurantCardList>
</StyledLeftSide>
<StyledRightSide>
<Map
clickMarker={clickMarker}
setBoundary={setBoundary}
markers={data.map(({ lat, lng, celebs }) => ({ position: { lat, lng }, celebs }))}
toggleMapExpand={toggleMapExpand}
/>
{currentRestaurant && (
<MapModal
modalOpen={modalOpen}
isVisible={isVisible}
onClickExit={closeModal}
modalRestaurantInfo={currentRestaurant}
/>
)}
<Map setBoundary={setBoundary} data={data} toggleMapExpand={toggleMapExpand} />
</StyledRightSide>
</StyledLayout>
<Footer />
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/styles/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const base = css`
--gray-4: #393939;
--gray-5: #2c2c2c;
--gray-6: #161616;
--orange: #ffb26b;
--orange-1: #ffb26b;
--orange-2: #eb982d;
--yellow: #ffd56f;
--black: #424242;
--white: #fff;
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/utils/getQuadrant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Coordinate } from '~/@types/map.types';

export type Quadrant = 1 | 2 | 3 | 4;

const getQuadrant = (center: Coordinate, target: Coordinate): Quadrant => {
const dx = target.lng - center.lng;
const dy = target.lat - center.lat;

if (dx > 0 && dy > 0) return 1;

if (dx < 0 && dy > 0) return 2;

if (dx < 0 && dy < 0) return 3;

if (dx > 0 && dy < 0) return 4;

return 1;
shackstack marked this conversation as resolved.
Show resolved Hide resolved
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오홍~! 깔끔하네요! 옛날 수학 공부하는 생각이...ㅎㅎ

이것도... 함수 추상화 측면적인 이야기이긴 한데... 아직 한 군데에서밖에 안쓰여서.. 일단 남겨보겠습니다! 반영은 원하신다면 해주세요! 지금도 괜찮다고 생각해요!

함수 네이밍이 getQuadrant 이고 인자가 중앙과 타겟이 있는거를 보아하니, 중앙에서 타겟이 어느 분면에 있는지 파악하는 함수라고 바로 와 닿았어요! 이 사분면을 구분하는 두 개의 인자가 지금은 위도, 경도인 Coordinate 로 타입이 좁혀져 있는데, 그냥 위도 경도가 아니더라도 숫자쌍으로도 받을 수 있게 타입을 확장해도 좋겠다고 생각했어요!

그렇지만 아마도..? 제레미의 원래 노림수대로 아직 쓰이는 곳이 위도, 경도에 종속되어 있으니 만약! 나중에 다른 곳에서도 사용한다면 그 때 확장하는 것도 찬성이에요~!


export default getQuadrant;