Skip to content

Commit

Permalink
feat: 레스토랑 카드에 loading 상태 추가 및 skeleton 반영 (#202)
Browse files Browse the repository at this point in the history
* feat: 마커 호버시 마커를 맨 앞으로 가져오기 (#192)

* style: 마커 호버시 마커 강조 (#192)

* feat: 마커 클릭시 레스토랑 카드 띄우기 (#192)

* refactor: 불필요한 코드 제거 (#192)

- 레스토랑 카드 클릭시 맵 모달 이벤트 제거
- 마커 클릭시 맵 모달 이벤트 제거

* feat: 마커클릭 시 마커 위치에 따라 카드모달 위치 조정 (#192)

* style: 레스토랑 오버레이 스타일 수정 (#192)

* refactor: restaurantCard 컴포넌트를 용도에 따라 스타일 다르게 설정 (#192)

* feat: 마커 클릭시 강조 효과 주기 (#192)

* Squashed commit of the following:

commit 3442d16
Author: Minjae Kim <[email protected]>
Date:   Fri Jul 28 15:06:21 2023 +0900

    design: 전체 카테고리에 해당하는 이미지 수정 (#195) (#196)

commit 2112803
Author: 황준승 <[email protected]>
Date:   Thu Jul 27 16:45:06 2023 +0900

    feat: 음식점 리스트 중복 필터링 기능 구현 (#186)

    * refactor:  지도 boundary 타입 추가 및 음식점 카테고리 타입 일부 수정 (#180)

    * feat: 셀럽 및 음식점 카테고리 별 필터링 기능 추가 (#180)

    * refactor: getQueryString 로직 분리 및 적용 (#180)

    * feat: CelebDropDown 및 CategoryNavbar에 전체 버튼 추가 (#184)

    * fix: 필터링 클릭 시 렌더링이 한 박자 늦게되는 오류 해결 (#184)

    * refactor: Map 컴포넌트에서 사용하지 않는 props 속성 제거 (#184)

    * refactor: celeb 전체를 나타내는 상태값을 -1로 변경 (#184)

    * feat: CelebDropDown blur 기능 추가 (#184)

    * fix: 불필요한 useEffect dependency 제거 (#184)

    * feat: Restaurant_Category에 전체 옵션 추가 (#184)

    * fix: CelebId 초기값 수정 (#184)

    * fix: API 명세서 수정에 따른 데이터 타입 변경 (#184)

    * refactor: NavButton props 프로퍼티 수정으로 인한 코드 수정 (#184)

    * refactor: css 선언방식을 삼항연산자를 && 로 변경 (#184)

    * fix: NavButton 불필요한 hover 기능 제거 (#184)

    Changed:
    hover 이벤트 && 연산자를 삼항연산자 사용으로 변경

    * refactor: NavButton 컴포넌트를 NavItem 컴포넌트로 네이밍 수정 (#184)

    ---------

    Co-authored-by: d0dam <[email protected]>
    Co-authored-by: Jeremy <[email protected]>

* feat: 다른 마커 클릭시 기존 마커 모달 닫기 기능 구현 (#192)

* feat: 음식점 카드에 사용하는 컴포넌트 스켈레톤 구현 (#199)

* refactor: 음식점 카드 리스트 컴포넌트 분리, 로딩 상태 추가 (#199)

* refactor: 이미지에 대해 loading lazy 속성 추가 (#199)

* refactor: 로딩 애니메이션을 자연스럽게 수정 (#199)

* refactor: Map에 data 로딩 상태 추가 (#199)

* feat: 전 음식점 갯수만큼 음식점 스켈레톤을 표시하도록 수정 (#199)

* refactor: Map이 로딩 상태일 때 스타일 추가 (#199)

* Squashed commit of the following:

commit 60f8707
Author: Jeremy <[email protected]>
Date:   Mon Jul 31 14:41:46 2023 +0900

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

    * feat: 마커 호버시 마커를 맨 앞으로 가져오기 (#192)

    * style: 마커 호버시 마커 강조 (#192)

    * feat: 마커 클릭시 레스토랑 카드 띄우기 (#192)

    * refactor: 불필요한 코드 제거 (#192)

    - 레스토랑 카드 클릭시 맵 모달 이벤트 제거
    - 마커 클릭시 맵 모달 이벤트 제거

    * feat: 마커클릭 시 마커 위치에 따라 카드모달 위치 조정 (#192)

    * style: 레스토랑 오버레이 스타일 수정 (#192)

    * refactor: restaurantCard 컴포넌트를 용도에 따라 스타일 다르게 설정 (#192)

    * feat: 마커 클릭시 강조 효과 주기 (#192)

    * Squashed commit of the following:

    commit 3442d16
    Author: Minjae Kim <[email protected]>
    Date:   Fri Jul 28 15:06:21 2023 +0900

        design: 전체 카테고리에 해당하는 이미지 수정 (#195) (#196)

    commit 2112803
    Author: 황준승 <[email protected]>
    Date:   Thu Jul 27 16:45:06 2023 +0900

        feat: 음식점 리스트 중복 필터링 기능 구현 (#186)

        * refactor:  지도 boundary 타입 추가 및 음식점 카테고리 타입 일부 수정 (#180)

        * feat: 셀럽 및 음식점 카테고리 별 필터링 기능 추가 (#180)

        * refactor: getQueryString 로직 분리 및 적용 (#180)

        * feat: CelebDropDown 및 CategoryNavbar에 전체 버튼 추가 (#184)

        * fix: 필터링 클릭 시 렌더링이 한 박자 늦게되는 오류 해결 (#184)

        * refactor: Map 컴포넌트에서 사용하지 않는 props 속성 제거 (#184)

        * refactor: celeb 전체를 나타내는 상태값을 -1로 변경 (#184)

        * feat: CelebDropDown blur 기능 추가 (#184)

        * fix: 불필요한 useEffect dependency 제거 (#184)

        * feat: Restaurant_Category에 전체 옵션 추가 (#184)

        * fix: CelebId 초기값 수정 (#184)

        * fix: API 명세서 수정에 따른 데이터 타입 변경 (#184)

        * refactor: NavButton props 프로퍼티 수정으로 인한 코드 수정 (#184)

        * refactor: css 선언방식을 삼항연산자를 && 로 변경 (#184)

        * fix: NavButton 불필요한 hover 기능 제거 (#184)

        Changed:
        hover 이벤트 && 연산자를 삼항연산자 사용으로 변경

        * refactor: NavButton 컴포넌트를 NavItem 컴포넌트로 네이밍 수정 (#184)

        ---------

        Co-authored-by: d0dam <[email protected]>
        Co-authored-by: Jeremy <[email protected]>

    * feat: 다른 마커 클릭시 기존 마커 모달 닫기 기능 구현 (#192)

    * style: 파일명 오류 수정 (#192)

    * refactor: baseURL 환경변수 설정 및 type import 분리

    * feat: RestaurantCard 컴포넌트 props 수정 (#192)

    onClick을 optional로 수정

    * refactor: getQuadrant 리팩터링 (#192)

    * style: 상태 네이밍 수정 (#192)

    mainPosition -> currentCenter

    * feat: 음식점 카드 호버시 해당 음식점 마커 강조 (#192)

    * design: 강조시 애니메이션 효과 추가 및 음식점 리스트 스타일 수정 (#192)

    * refactor: 음식점 카드 호버시 마커 강조 로직 변경 (#192)

    * refactor: 프로필 이미지 컴포넌트 Props 타입 수정 (#192)

    size: number => string

    * fix: setHoverId가 없을 때 default value 설정 (#192)

* fix: 충돌 해결 간 생긴 에러 및 lint 에러 수정 (#201)

---------

Co-authored-by: Jeremy <[email protected]>
  • Loading branch information
D0Dam and shackstack authored Jul 31, 2023
1 parent 60f8707 commit 2977855
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 50 deletions.
9 changes: 9 additions & 0 deletions frontend/src/@types/api.types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
export interface RestaurantListData {
content: RestaurantData[];
currentElementsCount: number;
currentPage: number;
pageSize: number;
totalElementsCount: number;
totalPage: number;
}

export interface RestaurantData {
id: number;
name: string;
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/components/@common/LoadingDots/LoadingDots.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default LoadingDots;

const StyledLoadingDots = styled.div`
display: flex;
gap: 0 0.6rem;
gap: 0 1.4rem;
& > div:nth-child(2) {
animation-delay: 0.14s;
Expand All @@ -30,13 +30,13 @@ const pulseAnimation = keyframes`
transform: scale(0);
}
90%, 100% {
transform: scale(1);
transform: scale(10);
}
`;

const StyledLoadingDot = styled.div`
width: 12px;
height: 12px;
width: 1.2px;
height: 1.2px;
border-radius: 50%;
background-color: var(--black);
Expand Down
30 changes: 22 additions & 8 deletions frontend/src/components/@common/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Wrapper, Status } from '@googlemaps/react-wrapper';
import { styled } from 'styled-components';
import OverlayMarker from './OverlayMarker';
import MapContent from './MapContent';
import OverlayMyLocation from './OverlayMyLocation';
import LoadingDots from '../LoadingDots';
Expand All @@ -12,6 +11,7 @@ import RightBracket from '~/assets/icons/right-bracket.svg';
import Minus from '~/assets/icons/minus.svg';
import Plus from '~/assets/icons/plus.svg';
import getQuadrant from '~/utils/getQuadrant';
import OverlayMarker from './OverlayMarker';

import type { Coordinate, CoordinateBoundary } from '~/@types/map.types';
import type { RestaurantData } from '~/@types/api.types';
Expand All @@ -21,18 +21,33 @@ interface MapProps {
hoveredId: number | null;
setBoundary: React.Dispatch<React.SetStateAction<CoordinateBoundary>>;
toggleMapExpand: () => void;
loadingData: boolean;
}

const render = (status: Status) => {
if (status === Status.FAILURE)
return <div>지도를 불러올 수 없습니다. 페이지를 새로고침 하거나 네트워크 연결을 다시 한 번 확인해주세요.</div>;
return <LoadingDots />;
return (
<StyledMapLoadingContainer>
<LoadingDots />
</StyledMapLoadingContainer>
);
};

const StyledMapLoadingContainer = styled.section`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-color: var(--gray-2);
`;

const JamsilCampus = { lat: 37.515271, lng: 127.1029949 };

function Map({ data, setBoundary, toggleMapExpand, hoveredId }: MapProps) {
const [center, setCenter] = useState<Coordinate>(JamsilCampus);
function Map({ data, setBoundary, toggleMapExpand, loadingData, hoveredId }: 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);
Expand Down Expand Up @@ -86,7 +101,7 @@ function Map({ data, setBoundary, toggleMapExpand, hoveredId }: MapProps) {
zoom={zoom}
center={center}
>
{data.map(({ celebs, ...restaurant }) => {
{data?.map(({ celebs, ...restaurant }) => {
const { lat, lng } = restaurant;
return (
<OverlayMarker
Expand All @@ -98,7 +113,7 @@ function Map({ data, setBoundary, toggleMapExpand, hoveredId }: MapProps) {
);
})}
{myPosition && <OverlayMyLocation position={myPosition} />}
{loading && (
{(loadingData || loading) && (
<LoadingUI>
<LoadingDots />
</LoadingUI>
Expand Down Expand Up @@ -132,8 +147,7 @@ const LoadingUI = styled.div`
right: calc(50% - 41px);
width: 82px;
padding: 1.6rem 2.4rem;
height: 40px;
`;

const StyledMyPositionButtonUI = styled.button`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { styled } from 'styled-components';
import { paintSkeleton } from '~/styles/common';

interface ProfileImageSkeletonProps {
size: number;
}

function ProfileImageSkeleton({ size }: ProfileImageSkeletonProps) {
return <StyledProfileImageSkeleton size={size} />;
}

export default ProfileImageSkeleton;

const StyledProfileImageSkeleton = styled.div<{ size: number }>`
${paintSkeleton}
width: ${({ size }) => (size ? `${size}px` : '100%')};
height: ${({ size }) => (size ? `${size}px` : 'auto')};
border-radius: 50%;
background: none;
`;
7 changes: 4 additions & 3 deletions frontend/src/components/RestaurantCard/RestaurantCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { styled } from 'styled-components';
import { BORDER_RADIUS, FONT_SIZE, truncateText } from '~/styles/common';
import { BORDER_RADIUS, FONT_SIZE, paintSkeleton, truncateText } from '~/styles/common';
import ProfileImage from '../@common/ProfileImage';
import { BASE_URL } from '~/App';

Expand Down Expand Up @@ -35,7 +35,7 @@ function RestaurantCard({

return (
<StyledContainer onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<StyledImage alt={`${name} 대표 이미지`} src={`${BASE_URL}images-data/${images[0].name}`} type={type} />
<StyledImage alt={`${name} 대표 이미지`} src={`${BASE_URL}/images-data/${images[0].name}`} type={type} />
<section>
<StyledInfo>
<StyledCategory>{category}</StyledCategory>
Expand Down Expand Up @@ -71,11 +71,12 @@ const StyledContainer = styled.div`
`;

const StyledImage = styled.img<{ type: 'list' | 'map' }>`
${paintSkeleton}
width: 100%;
aspect-ratio: 1.05 / 1;
border-radius: ${({ type }) =>
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0`};
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0 `};
object-fit: cover;
`;
Expand Down
92 changes: 92 additions & 0 deletions frontend/src/components/RestaurantCard/RestaurantCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { styled } from 'styled-components';
import ProfileImageSkeleton from '../@common/ProfileImage/ProfileImageSkeleton';
import { BORDER_RADIUS, paintSkeleton } from '~/styles/common';

function RestaurantCardSkeleton() {
return (
<StyledContainer>
<StyledImage />
<section>
<StyledInfo>
<StyledCategory />
<StyledName />
<StyledAddress />
<StyledAddress />
</StyledInfo>
<StyledProfileImageSection>
<ProfileImageSkeleton size={42} />
</StyledProfileImageSection>
</section>
</StyledContainer>
);
}

export default RestaurantCardSkeleton;

const StyledContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: start;
gap: 0.8rem;

width: 100%;
height: 100%;

& > section {
display: flex;
justify-content: space-between;
}

cursor: pointer;
`;

const StyledImage = styled.div`
${paintSkeleton}
width: 100%;
aspect-ratio: 1.05 / 1;

object-fit: cover;

border-radius: ${BORDER_RADIUS.md};
`;

const StyledInfo = styled.div`
display: flex;
flex: 1;
flex-direction: column;
gap: 0.4rem;

position: relative;

width: 100%;

padding: 0.4rem;
`;

const StyledName = styled.h5`
${paintSkeleton}
width: 100%;
height: 20px;

border-radius: ${BORDER_RADIUS.xs};
`;

const StyledAddress = styled.span`
${paintSkeleton}
width: 50%;
height: 12px;

border-radius: ${BORDER_RADIUS.xs};
`;

const StyledCategory = styled.span`
${paintSkeleton}
width: 40%;
height: 12px;

border-radius: ${BORDER_RADIUS.xs};
`;

const StyledProfileImageSection = styled.div`
align-self: flex-end;
`;
56 changes: 56 additions & 0 deletions frontend/src/components/RestaurantCardList/RestaurantCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { styled } from 'styled-components';
import { useEffect, useState } from 'react';
import RestaurantCard from '../RestaurantCard/RestaurantCard';
import { FONT_SIZE } from '~/styles/common';
import RestaurantCardListSkeleton from './RestaurantCardListSkeleton';

import type { RestaurantData, RestaurantListData } from '~/@types/api.types';

interface RestaurantCardListProps {
restaurantDataList: RestaurantListData | null;
loading: boolean;
setHoveredId: React.Dispatch<React.SetStateAction<number>>;
}

function RestaurantCardList({ restaurantDataList, loading, setHoveredId }: RestaurantCardListProps) {
const [prevCardNumber, setPrevCardNumber] = useState(18);

useEffect(() => {
if (restaurantDataList) setPrevCardNumber(restaurantDataList.currentElementsCount);
}, [restaurantDataList?.currentElementsCount]);

if (!restaurantDataList || loading) return <RestaurantCardListSkeleton cardNumber={prevCardNumber} />;

return (
<div>
<StyledCardListHeader>음식점 수 {restaurantDataList.totalElementsCount}</StyledCardListHeader>
<StyledRestaurantCardList>
{restaurantDataList.content?.map(({ celebs, ...restaurant }: RestaurantData) => (
<RestaurantCard restaurant={restaurant} celebs={celebs} size="42px" setHoveredId={setHoveredId} />
))}
</StyledRestaurantCardList>
</div>
);
}

export default RestaurantCardList;

const StyledCardListHeader = styled.p`
margin: 3.2rem 2.4rem;
font-size: ${FONT_SIZE.md};
`;

const StyledRestaurantCardList = styled.div`
display: grid;
gap: 4rem 2.4rem;
height: 100%;
margin: 0 2.4rem;
grid-template-columns: 1fr 1fr 1fr;
@media screen and (width <= 1240px) {
grid-template-columns: 1fr 1fr;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { styled } from 'styled-components';
import RestaurantCardSkeleton from '../RestaurantCard/RestaurantCardSkeleton';
import { BORDER_RADIUS, paintSkeleton } from '~/styles/common';

interface RestaurantCardListSkeletonProps {
cardNumber: number;
}

function RestaurantCardListSkeleton({ cardNumber }: RestaurantCardListSkeletonProps) {
return (
<div>
<StyledCardListHeader />
<StyledRestaurantCardList>
{Array.from({ length: cardNumber }, () => (
<RestaurantCardSkeleton />
))}
</StyledRestaurantCardList>
</div>
);
}

export default RestaurantCardListSkeleton;

const StyledCardListHeader = styled.p`
${paintSkeleton}
width: 35%;
height: 16px;
margin: 3.2rem 2.4rem;
border-radius: ${BORDER_RADIUS.xs};
`;

const StyledRestaurantCardList = styled.div`
display: grid;
gap: 4rem 2.4rem;
height: 100%;
margin: 0 2.4rem;
grid-template-columns: 1fr 1fr 1fr;
@media screen and (width <= 1240px) {
grid-template-columns: 1fr 1fr;
}
`;
3 changes: 3 additions & 0 deletions frontend/src/components/RestaurantCardList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import RestaurantCardList from './RestaurantCardList';

export default RestaurantCardList;
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';

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]);
}
Loading

0 comments on commit 2977855

Please sign in to comment.