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/#313: 건물 정보 UI 구현 #314

Merged
merged 30 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1a72565
Feat: 건물 정보를 요청하는 함수 구현
hwinkr Jan 27, 2024
bfb4cab
Chore: 건물 정보 컴포넌트에서 사용 할 위치 아이콘 타입 추가
hwinkr Jan 27, 2024
dc62676
Config: 건물 정보 타입 설정
hwinkr Jan 27, 2024
8472804
Config: 건물 정보 컴포넌트에서 사용할 이미지 크기 추가
hwinkr Jan 27, 2024
68eed13
Config: 건물 정보 컴포넌트에서 사용할 위치 아이콘 추가
hwinkr Jan 27, 2024
49fd65a
Feat(ToggleInfo): 토글 기능이 있는 정보를 보여주는 컴포넌트 구현
hwinkr Jan 27, 2024
9266b89
Feat: 하단 탭 스타일 변경
hwinkr Jan 27, 2024
42ea306
Chore: 건물 정보를 보여주는 컴포넌트 추가
hwinkr Jan 27, 2024
478d813
Refactor(overlay): 지도 페이지에서 사용하는 오버레이 로직 변경
hwinkr Jan 27, 2024
93784d4
Chore(add building code): 건물 정보 api 요청 시 사용할 건물 코드를 상수 객체에 추가
hwinkr Jan 27, 2024
a40ffb7
Config(add type): 건물 정보 api 요청 시 사용할 건물 코드 타입 추가
hwinkr Jan 27, 2024
4f926b8
Feat(useBuildingInfo): 건물 정보를 가져오는 커스텀 훅 구현
hwinkr Jan 27, 2024
e2389aa
Feat(useDragInfo): 건물 정보 컴포넌트를 드래그하기 위한 커스텀 훅 구현
hwinkr Jan 27, 2024
d621718
Refactor(useUserLocation): 사용자 위치 정보가 있는 경우 early return을 하도록 수정
hwinkr Jan 27, 2024
fc0fcad
Chore(add component): 지도 페이지에 건물 정보를 보여주는 컴포넌트 추가
hwinkr Jan 27, 2024
5435886
Chore(change icon name): 위치 아이콘 이름 변경
hwinkr Jan 27, 2024
e5dfbaa
Feat(Boundary): 컴포넌트의 드래그 범위를 설정하기 위한 컴포넌트 구현
hwinkr Jan 27, 2024
ee58033
Chore(delete unused file): 사용하지 않는 파일 삭제
hwinkr Jan 27, 2024
8755d72
Chore(change import path): 유틸 함수 import 경로 변경
hwinkr Jan 27, 2024
6dc28e3
Feat(BuildingInfo): 건물 정보 컴포넌트 구현
hwinkr Jan 27, 2024
5745d54
Feat(BuildingInfoToggle): 건물 정보 컴포넌트 여부를 결정하는 컴포넌트 구현
hwinkr Jan 27, 2024
0f85cac
Feat(FloorInfo, Content): 층별 정보를 보여주는 컴포넌트 구현
hwinkr Jan 27, 2024
4a5ed10
Feat(InfoContent): 건물 정보의 내용을 보여주는 컴포넌트 구현
hwinkr Jan 27, 2024
59c3a5d
Chore(change export path): 위도, 경도로 거리를 계산하는 함수의 폴더 위치 이동 반영
hwinkr Jan 27, 2024
1eba2ed
Chore(add import path): 건물 정보를 확인하는 함수를 utils 폴더로 이동 후 반영
hwinkr Jan 27, 2024
9fd79ae
Chore(change function name): 건물 검색 결과를 확인하는 함수 이름 변경
hwinkr Jan 27, 2024
aa51c2b
Feat(util function): 건물 정보와 관련된 유틸 함수 구현
hwinkr Jan 27, 2024
d9d872e
Feat(util function): 건물 정보 컴포넌트 드래그를 위한 유틸 함수 구현
hwinkr Jan 27, 2024
04ec8a6
Feat(util function): 사용자 위치와 관련된 유틸 함수 구현
hwinkr Jan 27, 2024
cbf5d41
Chore(change node version): 노드 버젼 변경
hwinkr Jan 29, 2024
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
2 changes: 1 addition & 1 deletion dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:16
FROM node:18
WORKDIR /usr/src/app
COPY package.json ./
RUN yarn
Expand Down
16 changes: 16 additions & 0 deletions src/@types/building-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface Room {
roomNumber: string;
roomName: string;
}

export type Floor = 'basement' | 'ground' | 'rooftop';

export type TotalFloorInfo = {
[key in Floor]: {
[key: string]: Room[];
};
};

export type FloorInfo = {
[key: string]: Room[];
};
1 change: 1 addition & 0 deletions src/@types/map.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type BuildingType = 'A' | 'B' | 'C' | 'D' | 'E';

export interface PKNUBuilding {
readonly buildingCode: string;
readonly buildingNumber: string;
readonly buildingName: string;
readonly latlng: [number, number];
Expand Down
5 changes: 3 additions & 2 deletions src/@types/styles/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export type IconKind =
| 'light'
| 'checkedRadio'
| 'uncheckedRadio'
| 'location'
| 'warning'
| 'account'
| 'language';
| 'language'
| 'myLocation'
| 'location';
2 changes: 1 addition & 1 deletion src/@types/styles/size.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CSSProperties } from 'react';

export type SizeOption = 'large' | 'medium' | 'small' | 'tiny';
export type SizeOption = 'large' | 'medium' | 'small' | 'tiny' | 'building';

export interface Size {
height: CSSProperties['height'];
Expand Down
16 changes: 16 additions & 0 deletions src/apis/building-info/fetch-building-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import http from '@apis/http';
import { SERVER_URL } from '@config/index';

const fetchBuildingInfo = async (buildingCode: string) => {
try {
const buildingInfo = await http.get(
`${SERVER_URL}/api/buildingInfo?code=${buildingCode}`,
);

return buildingInfo;
} catch (error) {
return error;
}
};

export default fetchBuildingInfo;
4 changes: 3 additions & 1 deletion src/components/Common/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
MdOutlineKeyboardArrowDown,
MdAssignmentInd,
MdLanguage,
MdOutlineLocationOn,
} from 'react-icons/md';

const ICON: { [key in IconKind]: IconType } = {
Expand Down Expand Up @@ -58,10 +59,11 @@ const ICON: { [key in IconKind]: IconType } = {
light: MdOutlineLightbulb,
uncheckedRadio: MdRadioButtonUnchecked,
checkedRadio: MdRadioButtonChecked,
location: MdOutlineMyLocation,
warning: MdOutlineError,
account: MdAssignmentInd,
language: MdLanguage,
myLocation: MdOutlineMyLocation,
location: MdOutlineLocationOn,
};

interface IconProps {
Expand Down
1 change: 1 addition & 0 deletions src/components/Common/Image/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const imageSize: ImageSize = {
medium: setSize(150),
small: setSize(100),
tiny: setSize(80),
building: setSize(100, 180),
};

const Image = ({
Expand Down
53 changes: 53 additions & 0 deletions src/components/Common/ToggleInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { THEME } from '@styles/ThemeProvider/theme';
import React, { useState } from 'react';

import Icon from '../Icon';

interface ToggleInfoProps {
infoTitle: () => JSX.Element;
infoDesc: () => JSX.Element;
}

const ToggleInfo = ({ infoTitle, infoDesc }: ToggleInfoProps) => {
const [showInfo, setShowInfo] = useState<boolean>(false);
const toggleInfo = () => setShowInfo((prevState) => !prevState);

return (
<>
<ToggleContainer showInfo={showInfo} onClick={toggleInfo}>
{infoTitle()}
<IconContainer>
<Icon kind="arrowDown" size="24" />
</IconContainer>
</ToggleContainer>
{showInfo && infoDesc()}
</>
);
};

export default ToggleInfo;

const ToggleContainer = styled.section<{ showInfo: boolean }>`
position: relative;
padding: 10px 0px 10px 0px;
display: flex;
align-items: center;

${({ showInfo }) => css`
& > span {
color: ${showInfo && THEME.PRIMARY};
}
& > div > svg {
transform: ${showInfo ? 'rotate(-180deg)' : 'rotate(0deg)'};
transition: all ease 0.3s;
}
`}
`;

const IconContainer = styled.div`
position: absolute;
right: 0;
display: flex;
`;
8 changes: 3 additions & 5 deletions src/components/FooterTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,17 @@ const FooterTab = () => {
};

const Footer = styled.div`
position: fixed;
bottom: 0;
display: flex;
justify-content: space-around;
align-items: center;

max-width: 480px;
width: 100%;
height: 60px;
padding: 15px 0px 15px 0px;
background-color: ${THEME.TEXT.WHITE};
position: fixed;
bottom: 0;
z-index: 2;

z-index: 999;
box-shadow: 0px -2px 6px rgba(99, 99, 99, 0.2);
`;

Expand Down
5 changes: 5 additions & 0 deletions src/components/Providers/MapProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
PknuMap,
RefreshButtons,
} from '@pages/Map/components';
import BuildingInfoToggle from '@pages/Map/components/BuildingInfo/BuildingInfoToggle';
import { Location } from '@type/map';
import React, { useState } from 'react';

Expand Down Expand Up @@ -37,8 +38,12 @@ Map.PknuMap = PknuMap;
Map.MapHeader = MapHeader;
Map.FilterButtons = FilterButtons;
Map.RefreshButtons = RefreshButtons;
Map.BuildingInfoToggle = BuildingInfoToggle;

const MapContainer = styled.div`
overflow: hidden;
max-width: 480px;
min-height: 100vh;
height: calc(100vh - 8vh);
display: flex;
flex-direction: column;
Expand Down
20 changes: 1 addition & 19 deletions src/components/Providers/OverlayProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,7 @@ interface OverlayProviderProps {
}

const OverlayProvider = ({ children }: OverlayProviderProps) => {
const userLocation = useUserLocation();
const { openModal } = useModals();

const handleOpenModal = (
title: string,
btn1Text: string,
onClick?: () => void,
btn2Text?: string,
) => {
openModal(
<Modal>
<Modal.ModalTitle title={title} />
<Modal.ModalButton text={btn1Text} />
{btn2Text && <Modal.ModalButton text={btn2Text} onClick={onClick} />}
</Modal>,
);
};

const customOverlay = new CustomOverlay(handleOpenModal, userLocation);
const customOverlay = new CustomOverlay();

return (
<OverlayContext.Provider value={customOverlay}>
Expand Down
56 changes: 7 additions & 49 deletions src/components/Providers/OverlayProvider/overlay.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { MODAL_BUTTON_MESSAGE, MODAL_MESSAGE } from '@constants/modal-messages';
import { PKNU_BUILDINGS } from '@constants/pknu-map';
import { THEME } from '@styles/ThemeProvider/theme';
import { BuildingType, Location, PKNUBuilding } from '@type/map';
import { hasLocationPermission } from '@utils/map';
import openLink from '@utils/router/openLink';
import { BuildingType, PKNUBuilding } from '@type/map';
import { CSSProperties } from 'react';

interface ICustomOverlay {
Expand All @@ -15,21 +12,10 @@ interface ICustomOverlay {
): void;
}

type OpenModal = (
title: string,
btn1Text: string,
onClick?: () => void,
btn2Text?: string,
) => void;

class CustomOverlay implements ICustomOverlay {
private overlays: Record<BuildingType, any[]>;
private openModal: OpenModal;
private userLocation: Location | null;

constructor(openModal: OpenModal, userLocation: Location | null) {
this.openModal = openModal;
this.userLocation = userLocation;
constructor() {
this.overlays = {
A: [],
B: [],
Expand All @@ -41,6 +27,7 @@ class CustomOverlay implements ICustomOverlay {

private isOverlayInMap(buildingType: BuildingType, building: PKNUBuilding) {
const type = buildingType as keyof typeof this.overlays;

if (this.overlays[type].length === 0) return false;
this.overlays[type].forEach((overlay) => {
if (overlay.cc.innerText === building.buildingName) return true;
Expand All @@ -53,43 +40,12 @@ class CustomOverlay implements ICustomOverlay {
return this.overlays[type].length >= PKNU_BUILDINGS[type].buildings.length;
}

private openNoLocationModal() {
this.openModal(
MODAL_MESSAGE.ALERT.NO_LOCATION_PERMISSON,
MODAL_BUTTON_MESSAGE.CLOSE,
);
}

private openConfirmRoutingModal(buildingInfo: PKNUBuilding) {
const { buildingNumber, buildingName, latlng } = buildingInfo;
const [lat, lng] = latlng;

const kakaoMapAppURL = `kakaomap://route?sp=${this.userLocation?.LAT},${this.userLocation?.LNG}&ep=${lat},${lng}`;
const kakaoMapWebURL = `https://map.kakao.com/link/from/현위치,${this.userLocation?.LAT},${this.userLocation?.LNG}/to/${buildingName},${lat},${lng}`;
const isKakaoMapInstalled = /KAKAOMAP/i.test(navigator.userAgent);
const openUrl = isKakaoMapInstalled ? kakaoMapAppURL : kakaoMapWebURL;

this.openModal(
`목적지(${buildingNumber})로 길찾기를 시작할까요?`,
MODAL_BUTTON_MESSAGE.NO,
() => openLink(openUrl),
MODAL_BUTTON_MESSAGE.YES,
);
}

private handleRoutingModal(building: PKNUBuilding) {
if (!this.userLocation) return;

hasLocationPermission(this.userLocation)
? this.openConfirmRoutingModal(building)
: this.openNoLocationModal();
}

private createOverlayContent(
activeColor: CSSProperties['color'],
building: PKNUBuilding,
) {
const content = document.createElement('span') as HTMLSpanElement;

Object.assign(content.style, {
backgroundColor: `${activeColor}`,
color: THEME.TEXT.WHITE,
Expand All @@ -98,9 +54,9 @@ class CustomOverlay implements ICustomOverlay {
fontSize: '10px',
fontWeight: 'bold',
});

const buildingNumberText = document.createTextNode(building.buildingNumber);
content.appendChild(buildingNumberText);
content.onclick = () => this.handleRoutingModal(building);

return content;
}
Expand All @@ -110,6 +66,7 @@ class CustomOverlay implements ICustomOverlay {
PKNU_BUILDINGS[type].activeColor,
building,
);

const overlay = new window.kakao.maps.CustomOverlay({
position: new window.kakao.maps.LatLng(
building.latlng[0],
Expand Down Expand Up @@ -138,6 +95,7 @@ class CustomOverlay implements ICustomOverlay {

addOverlay(buildingType: BuildingType, building: PKNUBuilding, map: any) {
const type = buildingType as keyof typeof this.overlays;

if (!this.isOverlayInMap(buildingType, building)) {
const overlay = this.createOverlay(buildingType, building);
overlay.setMap(map);
Expand Down
Loading
Loading