From 1a72565da698530692be9da58da8efa34fff12f6 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:19:21 +0900 Subject: [PATCH 01/30] =?UTF-8?q?Feat:=20=EA=B1=B4=EB=AC=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EC=9A=94=EC=B2=AD=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/building-info/fetch-building-info.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/apis/building-info/fetch-building-info.ts diff --git a/src/apis/building-info/fetch-building-info.ts b/src/apis/building-info/fetch-building-info.ts new file mode 100644 index 00000000..9923963e --- /dev/null +++ b/src/apis/building-info/fetch-building-info.ts @@ -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; From bfb4cabb48474d45e44c2d6c1af9322f01388221 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:20:20 +0900 Subject: [PATCH 02/30] =?UTF-8?q?Chore:=20=EA=B1=B4=EB=AC=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=82=AC=EC=9A=A9=20=ED=95=A0=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=EC=BD=98=20=ED=83=80=EC=9E=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/@types/styles/icon.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/@types/styles/icon.ts b/src/@types/styles/icon.ts index 364cad69..9daadbcf 100644 --- a/src/@types/styles/icon.ts +++ b/src/@types/styles/icon.ts @@ -24,7 +24,8 @@ export type IconKind = | 'light' | 'checkedRadio' | 'uncheckedRadio' - | 'location' | 'warning' | 'account' - | 'language'; + | 'language' + | 'myLocation' + | 'location'; From dc626763a084002a7fce04e911867e1809f4c207 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:21:00 +0900 Subject: [PATCH 03/30] =?UTF-8?q?Config:=20=EA=B1=B4=EB=AC=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=ED=83=80=EC=9E=85=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/@types/building-info.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/@types/building-info.ts diff --git a/src/@types/building-info.ts b/src/@types/building-info.ts new file mode 100644 index 00000000..d462e59b --- /dev/null +++ b/src/@types/building-info.ts @@ -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[]; +}; From 8472804aa68a7bd7acbe91ead8afe3a612d3f03f Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:21:55 +0900 Subject: [PATCH 04/30] =?UTF-8?q?Config:=20=EA=B1=B4=EB=AC=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=ED=81=AC=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/@types/styles/size.ts | 2 +- src/components/Common/Image/index.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/@types/styles/size.ts b/src/@types/styles/size.ts index 75159751..10b73b69 100644 --- a/src/@types/styles/size.ts +++ b/src/@types/styles/size.ts @@ -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']; diff --git a/src/components/Common/Image/index.tsx b/src/components/Common/Image/index.tsx index c4572406..b9a24ee7 100644 --- a/src/components/Common/Image/index.tsx +++ b/src/components/Common/Image/index.tsx @@ -8,6 +8,7 @@ const imageSize: ImageSize = { medium: setSize(150), small: setSize(100), tiny: setSize(80), + building: setSize(100, 180), }; const Image = ({ From 68eed138115b4bb17a9a1d79339ab069cc2a7708 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:22:20 +0900 Subject: [PATCH 05/30] =?UTF-8?q?Config:=20=EA=B1=B4=EB=AC=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Common/Icon/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Common/Icon/index.tsx b/src/components/Common/Icon/index.tsx index 06c0a05e..a2e26e92 100644 --- a/src/components/Common/Icon/index.tsx +++ b/src/components/Common/Icon/index.tsx @@ -30,6 +30,7 @@ import { MdOutlineKeyboardArrowDown, MdAssignmentInd, MdLanguage, + MdOutlineLocationOn, } from 'react-icons/md'; const ICON: { [key in IconKind]: IconType } = { @@ -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 { From 49fd65a5f938781f8fd429fa3a0728532f2d841c Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:25:21 +0900 Subject: [PATCH 06/30] =?UTF-8?q?Feat(ToggleInfo):=20=ED=86=A0=EA=B8=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=B4=20=EC=9E=88=EB=8A=94=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스타일을 외부에서 주입할 수 있도록 JSX.Element를 반환하는 함수를 props로 전달하도록 구현(render props) - FAQ 페이지에서도 해당 컴포넌트를 사용하도록 변경할 예정 --- src/components/Common/ToggleInfo/index.tsx | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/components/Common/ToggleInfo/index.tsx diff --git a/src/components/Common/ToggleInfo/index.tsx b/src/components/Common/ToggleInfo/index.tsx new file mode 100644 index 00000000..8c50d351 --- /dev/null +++ b/src/components/Common/ToggleInfo/index.tsx @@ -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(false); + const toggleInfo = () => setShowInfo((prevState) => !prevState); + + return ( + <> + + {infoTitle()} + + + + + {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; +`; From 9266b89de279739f23ffd49b8ccff069eda467c3 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:26:40 +0900 Subject: [PATCH 07/30] =?UTF-8?q?Feat:=20=ED=95=98=EB=8B=A8=20=ED=83=AD=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 건물 정보를 보여주는 컴포넌트가 마운트 되면, box-shadow가 사라지는 문제를 해결하기 위해서 z-index를 변경 --- src/components/FooterTab/index.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/FooterTab/index.tsx b/src/components/FooterTab/index.tsx index 545fcd6d..a8352f9f 100644 --- a/src/components/FooterTab/index.tsx +++ b/src/components/FooterTab/index.tsx @@ -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); `; From 42ea306c4a7cf339f14befd0a7c91c5aa9423a9b Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:29:00 +0900 Subject: [PATCH 08/30] =?UTF-8?q?Chore:=20=EA=B1=B4=EB=AC=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Providers/MapProvider/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/Providers/MapProvider/index.tsx b/src/components/Providers/MapProvider/index.tsx index 2d0c7704..df17c8ed 100644 --- a/src/components/Providers/MapProvider/index.tsx +++ b/src/components/Providers/MapProvider/index.tsx @@ -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'; @@ -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; From 478d813f00f12870ed24e3b7c7ea2183c969b8e0 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:29:44 +0900 Subject: [PATCH 09/30] =?UTF-8?q?Refactor(overlay):=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8A=94=20=EC=98=A4=EB=B2=84=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에는 오버레이 onClick 이벤트 발생 시 길찾기를 확인하는 모달을 렌더링 했어야 했는데, 책임이 변경되면서 모달 로직이 필요 없어져 해당 로직을 전부 삭제 --- .../Providers/OverlayProvider/index.tsx | 20 +------ .../Providers/OverlayProvider/overlay.ts | 56 +++---------------- 2 files changed, 8 insertions(+), 68 deletions(-) diff --git a/src/components/Providers/OverlayProvider/index.tsx b/src/components/Providers/OverlayProvider/index.tsx index 8a1a2578..356f192e 100644 --- a/src/components/Providers/OverlayProvider/index.tsx +++ b/src/components/Providers/OverlayProvider/index.tsx @@ -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( - - - - {btn2Text && } - , - ); - }; - - const customOverlay = new CustomOverlay(handleOpenModal, userLocation); + const customOverlay = new CustomOverlay(); return ( diff --git a/src/components/Providers/OverlayProvider/overlay.ts b/src/components/Providers/OverlayProvider/overlay.ts index c19708f1..6ee04df7 100644 --- a/src/components/Providers/OverlayProvider/overlay.ts +++ b/src/components/Providers/OverlayProvider/overlay.ts @@ -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 { @@ -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; - private openModal: OpenModal; - private userLocation: Location | null; - constructor(openModal: OpenModal, userLocation: Location | null) { - this.openModal = openModal; - this.userLocation = userLocation; + constructor() { this.overlays = { A: [], B: [], @@ -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; @@ -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, @@ -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; } @@ -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], @@ -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); From 93784d4e9a3168e6ebb4b6e3b5a02ef490520d27 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:35:29 +0900 Subject: [PATCH 10/30] =?UTF-8?q?Chore(add=20building=20code):=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=20api=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=82=AC=EC=9A=A9=ED=95=A0=20=EA=B1=B4=EB=AC=BC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A5=BC=20=EC=83=81=EC=88=98=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants/pknu-map.ts | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/constants/pknu-map.ts b/src/constants/pknu-map.ts index f217fb99..7690a38d 100644 --- a/src/constants/pknu-map.ts +++ b/src/constants/pknu-map.ts @@ -13,41 +13,49 @@ export const PKNU_BUILDINGS: PKNUBuildings = { activeColor: '#FF569E', buildings: [ { + buildingCode: 'B0000001', buildingNumber: 'A11', buildingName: '대학본부', latlng: [35.13397705691482, 129.10312908129794], }, { + buildingCode: 'B0000002', buildingNumber: 'A12', buildingName: '웅비관', latlng: [35.13444674928486, 129.1031985811075], }, { + buildingCode: 'B0000003', buildingNumber: 'A13', buildingName: '누리관', latlng: [35.134732247837064, 129.10310188800958], }, { + buildingCode: 'B0000013', buildingNumber: 'A15', buildingName: '향파관', latlng: [35.135256431017474, 129.10288500581757], }, { + buildingCode: 'B0000063', buildingNumber: 'A21', buildingName: '미래관', latlng: [35.13393257601037, 129.10218728388455], }, { + buildingCode: 'B0000011', buildingNumber: 'A22', buildingName: '디자인관', latlng: [35.134206208658554, 129.10147854379952], }, { + buildingCode: 'B0000012', buildingNumber: 'A23', buildingName: '나래관', latlng: [35.134827192477715, 129.10178520781048], }, { + buildingCode: 'B0000047', buildingNumber: 'A26', buildingName: '부산창업카페 2호점', latlng: [35.135198384658516, 129.10116672530137], @@ -58,36 +66,43 @@ export const PKNU_BUILDINGS: PKNUBuildings = { activeColor: '#FF9B29', buildings: [ { + buildingCode: 'B0000016', buildingNumber: 'B11', buildingName: '위드센터', latlng: [35.13400446186596, 129.10583108004366], }, { + buildingCode: 'B0000015', buildingNumber: 'B12', buildingName: '나비센터', latlng: [35.134004748015656, 129.1063348228572], }, { + buildingCode: 'B0000005', buildingNumber: 'B13', buildingName: '충무관', latlng: [35.13498344411026, 129.10524198684462], }, { + buildingCode: 'B0000018', buildingNumber: 'B14', buildingName: '환경해양관', latlng: [35.13498225522856, 129.10634867710766], }, { + buildingCode: 'B0000006', buildingNumber: 'B15', buildingName: '자연과학1관', latlng: [35.13550268883498, 129.10543781395603], }, { + buildingCode: 'B0000064', buildingNumber: 'B21', buildingName: '가온관', latlng: [35.1339439662216, 129.10503421773143], }, { + buildingCode: 'B0000017', buildingNumber: 'B22', buildingName: '청운관', latlng: [35.13436049066275, 129.10478989598502], @@ -98,56 +113,67 @@ export const PKNU_BUILDINGS: PKNUBuildings = { activeColor: '#8FC049', buildings: [ { + buildingCode: 'B0000113', buildingNumber: 'C11', buildingName: '수산질병관리원', latlng: [35.13369791744112, 129.10868013710467], }, { + buildingCode: 'B0000019', buildingNumber: 'C12', buildingName: '장영실관', latlng: [35.13473949619139, 129.1089014835836], }, { + buildingCode: 'B0000020', buildingNumber: 'C13', buildingName: '해양공동연구관', latlng: [35.135455506575745, 129.10905796000708], }, { + buildingCode: 'B0000125', buildingNumber: 'C14', buildingName: '부경대학교 어린이집', latlng: [35.134968609898955, 129.1095911476619], }, { + buildingCode: 'B0000010', buildingNumber: 'C21', buildingName: '수산과학관', latlng: [35.133483825666744, 129.10779091303866], }, { + buildingCode: 'B0000009', buildingNumber: 'C22', buildingName: '건축관', latlng: [35.13461392516209, 129.10770616016015], }, { + buildingCode: 'B0000007', buildingNumber: 'C23', buildingName: '호연관', latlng: [35.13516362219819, 129.10770602771154], }, { + buildingCode: 'B0000008', buildingNumber: 'C24', buildingName: '자연과학2관', latlng: [35.13561514272967, 129.10766771682054], }, { + buildingCode: 'B0000120', buildingNumber: 'C25', buildingName: '인문사회경영관', latlng: [35.134130687473196, 129.1077646460986], }, { + buildingCode: 'B0000023', buildingNumber: 'C27', buildingName: '수조실험동', latlng: [35.13302052706407, 129.10735244658267], }, { + buildingCode: 'B0000049', buildingNumber: 'C28', buildingName: '아름관', latlng: [35.13297601808968, 129.1079671063524], @@ -158,41 +184,49 @@ export const PKNU_BUILDINGS: PKNUBuildings = { activeColor: '#FFC801', buildings: [ { + buildingCode: 'B0000041', buildingNumber: 'D12', buildingName: '테니스장', latlng: [35.13190780493772, 129.10618953917788], }, { + buildingCode: 'B0000025', buildingNumber: 'D13', buildingName: '대운동장', latlng: [35.132864569032215, 129.10621774816596], }, { + buildingCode: 'B0000126', buildingNumber: 'D14', buildingName: '한울관', latlng: [35.132256439236, 129.1069844702002], }, { + buildingCode: 'B0000280', buildingNumber: 'D15', buildingName: '창의관', latlng: [35.132942918173015, 129.1068924664527], }, { + buildingCode: 'B0000027', buildingNumber: 'D21', buildingName: '대학극장', latlng: [35.132302199965665, 129.10500017213562], }, { + buildingCode: 'B0000024', buildingNumber: 'D22', buildingName: '체육관', latlng: [35.13316402584487, 129.1048002278217], }, { + buildingCode: 'B0000261', buildingNumber: 'D23', buildingName: '안전관리관', latlng: [35.13230801606284, 129.1051832332297], }, { + buildingCode: 'B0000260', buildingNumber: 'D24', buildingName: '수상레저관', latlng: [35.13273884835734, 129.10476459993706], @@ -203,36 +237,43 @@ export const PKNU_BUILDINGS: PKNUBuildings = { activeColor: '#31A4E9', buildings: [ { + buildingCode: 'B0000032', buildingNumber: 'E11', buildingName: '세종1관', latlng: [35.13111642272434, 129.1050436853718], }, { + buildingCode: 'B0000123', buildingNumber: 'E12', buildingName: '세종2관', latlng: [35.13112044963247, 129.10414663049266], }, { + buildingCode: 'B0000128', buildingNumber: 'E13', buildingName: '공학1관', latlng: [35.13166260843009, 129.103170430803], }, { + buildingCode: 'B0000036', buildingNumber: 'E14', buildingName: '학술정보관', latlng: [35.13251893742042, 129.10393622427503], }, { + buildingCode: 'B0000131', buildingNumber: 'E21', buildingName: '공학2관', latlng: [35.13158970912741, 129.10256856014524], }, { + buildingCode: 'B0000114', buildingNumber: 'E22', buildingName: '장보고관', latlng: [35.133090750102795, 129.10291383413244], }, { + buildingCode: 'B0000042', buildingNumber: 'E29', buildingName: '양어장관리사', latlng: [35.13301817930939, 129.10152110317765], From a40ffb73af18b3c130442449dc2676acf30d4acf Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:37:39 +0900 Subject: [PATCH 11/30] =?UTF-8?q?Config(add=20type):=20=EA=B1=B4=EB=AC=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20api=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20=EA=B1=B4=EB=AC=BC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=ED=83=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/@types/map.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/@types/map.ts b/src/@types/map.ts index 2e9e3962..d0a21cbd 100644 --- a/src/@types/map.ts +++ b/src/@types/map.ts @@ -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]; From 4f926b8e9550c290ab43cf19c410a02c3a7a560e Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:39:21 +0900 Subject: [PATCH 12/30] =?UTF-8?q?Feat(useBuildingInfo):=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useBuildingInfo.ts | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/hooks/useBuildingInfo.ts diff --git a/src/hooks/useBuildingInfo.ts b/src/hooks/useBuildingInfo.ts new file mode 100644 index 00000000..eb4a7e26 --- /dev/null +++ b/src/hooks/useBuildingInfo.ts @@ -0,0 +1,71 @@ +import fetchBuildingInfo from '@apis/building-info/fetch-building-info'; +import { PKNU_BUILDINGS } from '@constants/pknu-map'; +import { TotalFloorInfo } from '@type/building-info'; +import { BuildingType } from '@type/map'; +import { AxiosResponse } from 'axios'; +import { CSSProperties, useEffect, useState } from 'react'; + +interface BuildingInfo { + buildingCode: string; + buildingName: string; + latlng: [number, number]; + color: CSSProperties['color']; +} + +const getBuildingInfo = (buildingNumber: string) => { + const buildingTypes = Object.keys(PKNU_BUILDINGS) as BuildingType[]; + + for (const type of buildingTypes) { + for (const building of PKNU_BUILDINGS[type].buildings) { + if (building.buildingNumber !== buildingNumber) continue; + + return { + buildingCode: building.buildingCode, + buildingName: building.buildingName, + color: PKNU_BUILDINGS[type].activeColor, + latlng: building.latlng, + }; + } + } +}; + +const useBuildingInfo = (buildingNumber: string) => { + const [floorInfo, setFloorInfo] = useState( + {} as TotalFloorInfo, + ); + + const { buildingCode, buildingName, color, latlng } = getBuildingInfo( + buildingNumber, + ) as BuildingInfo; + + const buildingInfo = { + buildingName, + color, + imgPath: `https://www.pknu.ac.kr/imageView.do?target=campus&cd=${buildingCode}`, + latlng, + }; + + useEffect(() => { + const getBuildingInfo = async () => { + try { + const response = (await fetchBuildingInfo( + buildingCode as string, + )) as AxiosResponse; + const fetchedFloorInfo = response.data; + + setFloorInfo(fetchedFloorInfo as TotalFloorInfo); + } catch (error) { + return error; + } + }; + + getBuildingInfo(); + }, []); + + return { + floorInfo, + buildingInfo, + }; +}; + +export default useBuildingInfo; From e2389aa214f3c9bba8833e893cd0f199a922bfa9 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:41:19 +0900 Subject: [PATCH 13/30] =?UTF-8?q?Feat(useDragInfo):=20=EA=B1=B4=EB=AC=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EB=93=9C=EB=9E=98=EA=B7=B8=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useDragInfo.ts | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/hooks/useDragInfo.ts diff --git a/src/hooks/useDragInfo.ts b/src/hooks/useDragInfo.ts new file mode 100644 index 00000000..686747c8 --- /dev/null +++ b/src/hooks/useDragInfo.ts @@ -0,0 +1,46 @@ +import { inrange, registDragEvent } from '@utils/map/regist-drag-event'; +import { useEffect, useState } from 'react'; + +const DEFAULT_HEIGHT = 300; +const BOUNDARY_MARGIN = 5; +const MIN_H = 80; + +interface InfoPosition { + top: number; + height: number; +} + +const useDragInfo = (boundary: React.RefObject) => { + const [{ top, height }, setPosition] = useState({ + top: -DEFAULT_HEIGHT, + height: 0, + }); + + useEffect(() => { + const boundaryInfo = boundary.current?.getBoundingClientRect(); + + if (!boundaryInfo) return; + + setPosition({ + top: boundaryInfo.height / 2, + height: boundaryInfo.height / 2, + }); + }, []); + + const handleDrag = registDragEvent((deltaY) => { + setPosition({ + top: inrange(top + deltaY, BOUNDARY_MARGIN, top + height - MIN_H), + height: inrange(height - deltaY, MIN_H, top + height - BOUNDARY_MARGIN), + }); + }, true); + + return { + currentPosition: { + top, + height, + }, + handleDrag, + }; +}; + +export default useDragInfo; From d62171874b513ef2dc1d6d5a23ce5607b125ddc2 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:45:18 +0900 Subject: [PATCH 14/30] =?UTF-8?q?Refactor(useUserLocation):=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=9C=84=EC=B9=98=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EA=B0=80=20=EC=9E=88=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20early=20r?= =?UTF-8?q?eturn=EC=9D=84=20=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 건물 정보 컴포넌트가 렌더링 될 경우 setUserLocation 함수를 호출해서 지도 페이지 전체가 리렌더링되는 문제가 발생했고, 이를 해결하기 위해서 early return 패턴을 적용 --- src/hooks/useUserLocation.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hooks/useUserLocation.ts b/src/hooks/useUserLocation.ts index 3abac9b9..530a348e 100644 --- a/src/hooks/useUserLocation.ts +++ b/src/hooks/useUserLocation.ts @@ -25,6 +25,8 @@ const useUserLocation = () => { }; useEffect(() => { + if (userLocation) return; + if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(success, failed, { enableHighAccuracy: true, From fc0fcadfbd22e613af5bc74ae45b3eed713b72ad Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:46:23 +0900 Subject: [PATCH 15/30] =?UTF-8?q?Chore(add=20component):=20=EC=A7=80?= =?UTF-8?q?=EB=8F=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=B3=B4=EC=97=AC?= =?UTF-8?q?=EC=A3=BC=EB=8A=94=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Map/index.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pages/Map/index.tsx b/src/pages/Map/index.tsx index 1f571044..81ea7785 100644 --- a/src/pages/Map/index.tsx +++ b/src/pages/Map/index.tsx @@ -1,4 +1,5 @@ import Map from '@components/Providers/MapProvider'; +import { useEffect } from 'react'; declare global { interface Window { @@ -7,12 +8,21 @@ declare global { } const MapPage = () => { + useEffect(() => { + document.body.style.overflow = 'hidden'; + + return () => { + document.body.style.overflow = 'unset'; + }; + }, []); + return ( + ); }; From 54358869f220d6b8f832dd211c8aff711eb59b3c Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:47:51 +0900 Subject: [PATCH 16/30] =?UTF-8?q?Chore(change=20icon=20name):=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Map/components/RefreshButtons.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/Map/components/RefreshButtons.tsx b/src/pages/Map/components/RefreshButtons.tsx index 49303995..32cbca49 100644 --- a/src/pages/Map/components/RefreshButtons.tsx +++ b/src/pages/Map/components/RefreshButtons.tsx @@ -39,7 +39,7 @@ const RefreshButtons = () => { return ( handleMapCenter(userLocation)}> - + handleMapCenter(PKNU_MAP_CENTER)}> @@ -51,13 +51,13 @@ const RefreshButtons = () => { export default RefreshButtons; const IconContainer = styled.div` - width: 95%; position: absolute; top: 6rem; - z-index: 999; + right: 0; + z-index: 3; padding: 1rem; + width: 10%; gap: 10px; - display: flex; flex-direction: column; align-items: flex-end; From e5dfbaac50f676fa8d937f069d3289e8a502421b Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 19:56:07 +0900 Subject: [PATCH 17/30] =?UTF-8?q?Feat(Boundary):=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=9D=98=20=EB=93=9C=EB=9E=98=EA=B7=B8=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84=EB=A5=BC=20=EC=84=A4=EC=A0=95=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 해당 컴포넌트의 높이를 참조하기 위해서 useRef 훅을 사용 - useRef 훅을 전달할 수 있도록 React.forwardRef 사용 --- .../Map/components/BuildingInfo/Boundary.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/pages/Map/components/BuildingInfo/Boundary.tsx diff --git a/src/pages/Map/components/BuildingInfo/Boundary.tsx b/src/pages/Map/components/BuildingInfo/Boundary.tsx new file mode 100644 index 00000000..18ef6bc3 --- /dev/null +++ b/src/pages/Map/components/BuildingInfo/Boundary.tsx @@ -0,0 +1,15 @@ +import styled from '@emotion/styled'; +import React from 'react'; + +export default React.forwardRef>( + function Boundary(props, ref) { + return ; + }, +); + +const StyledBondary = styled.div` + position: relative; + min-height: 75vh; + background-color: transparent; + top: calc(100vh - 90px - 75vh); +`; From ee58033d04930d3986e6f40808e807fdba4bdddc Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:01:51 +0900 Subject: [PATCH 18/30] =?UTF-8?q?Chore(delete=20unused=20file):=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Map/handlers/distance.ts | 23 ----------------------- src/utils/map/check-location.ts | 8 -------- src/utils/map/get-building-info.ts | 23 ----------------------- src/utils/map/index.ts | 2 -- src/utils/map/location-permission.ts | 10 ---------- 5 files changed, 66 deletions(-) delete mode 100644 src/pages/Map/handlers/distance.ts delete mode 100644 src/utils/map/check-location.ts delete mode 100644 src/utils/map/get-building-info.ts delete mode 100644 src/utils/map/index.ts delete mode 100644 src/utils/map/location-permission.ts diff --git a/src/pages/Map/handlers/distance.ts b/src/pages/Map/handlers/distance.ts deleted file mode 100644 index c1801341..00000000 --- a/src/pages/Map/handlers/distance.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PKNU_MAP_CENTER } from '@constants/pknu-map'; - -const degreeToRadian = (deg: number) => deg * (Math.PI / 180); - -const getHaversineDistance = (lat: number, lng: number) => { - const R = 6371000; - - const dLat = degreeToRadian(PKNU_MAP_CENTER.LAT - lat); - const dLon = degreeToRadian(PKNU_MAP_CENTER.LNG - lng); - - const halfSideLength = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(degreeToRadian(PKNU_MAP_CENTER.LAT)) * - Math.cos(degreeToRadian(lat)) * - Math.sin(dLon / 2) * - Math.sin(dLon / 2); - const angularDistance = - 2 * Math.atan2(Math.sqrt(halfSideLength), Math.sqrt(1 - halfSideLength)); - - return R * angularDistance; -}; - -export default getHaversineDistance; diff --git a/src/utils/map/check-location.ts b/src/utils/map/check-location.ts deleted file mode 100644 index e23f37de..00000000 --- a/src/utils/map/check-location.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getHaversineDistance } from '@pages/Map/handlers'; - -const isUserInShcool = (lat: number, lng: number) => { - const maxDistance = 450; - return getHaversineDistance(lat, lng) <= maxDistance; -}; - -export default isUserInShcool; diff --git a/src/utils/map/get-building-info.ts b/src/utils/map/get-building-info.ts deleted file mode 100644 index 4a626bb6..00000000 --- a/src/utils/map/get-building-info.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { PKNU_BUILDINGS } from '@constants/pknu-map'; -import { BuildingType } from '@type/map'; - -const getBuildingInfo = ( - keyword: string, -): [BuildingType, number] | undefined => { - const formattedKeyword = keyword.replaceAll(' ', '').toUpperCase(); - - for (const buildingType of Object.keys(PKNU_BUILDINGS)) { - const index = PKNU_BUILDINGS[ - buildingType as BuildingType - ].buildings.findIndex( - (PKNU_BUILDING) => - PKNU_BUILDING.buildingName === formattedKeyword || - PKNU_BUILDING.buildingNumber === formattedKeyword, - ); - if (index !== -1) return [buildingType as BuildingType, index]; - } - - return; -}; - -export default getBuildingInfo; diff --git a/src/utils/map/index.ts b/src/utils/map/index.ts deleted file mode 100644 index 5bf0bc07..00000000 --- a/src/utils/map/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as hasLocationPermission } from './location-permission'; -export { default as isUserInShcool } from './check-location'; diff --git a/src/utils/map/location-permission.ts b/src/utils/map/location-permission.ts deleted file mode 100644 index 3f7a01c8..00000000 --- a/src/utils/map/location-permission.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NO_PROVIDE_LOCATION } from '@constants/pknu-map'; -import { Location } from '@type/map'; - -const hasLocationPermission = (location: Location | null) => { - return ( - location && JSON.stringify(location) !== JSON.stringify(NO_PROVIDE_LOCATION) - ); -}; - -export default hasLocationPermission; From 8755d725899d0e46945f3c22e9ce4509c1cc1844 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:13:46 +0900 Subject: [PATCH 19/30] =?UTF-8?q?Chore(change=20import=20path):=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20import=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Map/components/MapHeader.tsx | 2 +- src/pages/Map/components/PknuMap.tsx | 2 +- src/pages/Map/components/RefreshButtons.tsx | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/Map/components/MapHeader.tsx b/src/pages/Map/components/MapHeader.tsx index 7a1f1cc6..687ef749 100644 --- a/src/pages/Map/components/MapHeader.tsx +++ b/src/pages/Map/components/MapHeader.tsx @@ -8,7 +8,7 @@ import useOverlays from '@hooks/useOverlays'; import useToasts from '@hooks/useToast'; import { THEME } from '@styles/ThemeProvider/theme'; import { BuildingType, PKNUBuilding } from '@type/map'; -import getBuildingInfo from '@utils/map/get-building-info'; +import { getBuildingInfo } from '@utils/map/building-info'; import React, { useRef } from 'react'; const MapHeader = () => { diff --git a/src/pages/Map/components/PknuMap.tsx b/src/pages/Map/components/PknuMap.tsx index 672e4d43..2009cdfe 100644 --- a/src/pages/Map/components/PknuMap.tsx +++ b/src/pages/Map/components/PknuMap.tsx @@ -4,7 +4,7 @@ import styled from '@emotion/styled'; import useMap from '@hooks/useMap'; import useModals from '@hooks/useModals'; import useUserLocation from '@hooks/useUserLocation'; -import { isUserInShcool } from '@utils/map'; +import { isUserInShcool } from '@utils/map/user-location'; import React, { useEffect } from 'react'; import { handleMapBoundary } from '../handlers'; diff --git a/src/pages/Map/components/RefreshButtons.tsx b/src/pages/Map/components/RefreshButtons.tsx index 32cbca49..4ec6f514 100644 --- a/src/pages/Map/components/RefreshButtons.tsx +++ b/src/pages/Map/components/RefreshButtons.tsx @@ -7,7 +7,10 @@ import useToasts from '@hooks/useToast'; import useUserLocation from '@hooks/useUserLocation'; import { THEME } from '@styles/ThemeProvider/theme'; import { Location } from '@type/map'; -import { hasLocationPermission, isUserInShcool } from '@utils/map'; +import { + hasLocationPermission, + isUserInShcool, +} from '@utils/map/user-location'; import React from 'react'; const RefreshButtons = () => { From 6dc28e30bef2c803ee0333407e72796d8f1254a4 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:17:52 +0900 Subject: [PATCH 20/30] =?UTF-8?q?Feat(BuildingInfo):=20=EA=B1=B4=EB=AC=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컴포넌트를 드래그해서 높이를 조절할 수 있도록 top, height 조절 - 컴포넌트 영역 밖을 클릭하는 경우 해당 컴포넌트를 언마운트 하도록 구현 --- .../components/BuildingInfo/BuildingInfo.tsx | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/pages/Map/components/BuildingInfo/BuildingInfo.tsx diff --git a/src/pages/Map/components/BuildingInfo/BuildingInfo.tsx b/src/pages/Map/components/BuildingInfo/BuildingInfo.tsx new file mode 100644 index 00000000..2cd1df91 --- /dev/null +++ b/src/pages/Map/components/BuildingInfo/BuildingInfo.tsx @@ -0,0 +1,80 @@ +import styled from '@emotion/styled'; +import useDragInfo from '@hooks/useDragInfo'; +import React, { useRef } from 'react'; + +import Boundary from './Boundary'; +import InfoContent from './InfoContent'; + +const shouldUnmountInfo = (className: string) => { + return className === 'info-background' || className === 'info-boundary'; +}; + +interface BuildingInfoProps { + buildingNumber: string; + unmountInfo: () => void; +} + +const BuildingInfo = ({ buildingNumber, unmountInfo }: BuildingInfoProps) => { + const boundaryRef = useRef(null); + const { + currentPosition: { top, height }, + handleDrag, + } = useDragInfo(boundaryRef); + + const handleUnmount = (e: React.MouseEvent) => { + const clickedElement = e.target as HTMLElement; + const className = clickedElement.classList[0]; + + if (!shouldUnmountInfo(className)) return; + unmountInfo(); + }; + + return ( + + +
+ + + + +
+
+
+ ); +}; + +export default BuildingInfo; + +const BackGround = styled.div` + position: absolute; + min-height: calc(100vh - 90px); + max-width: 480px; + top: 0; + left: 0; + right: 0; + margin: auto; + background-color: rgba(0, 0, 0, 0.3); + z-index: 999; +`; + +const CursorContainer = styled.div` + position: absolute; + z-index: 999; + height: 30px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: white; + cursor: n-resize; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +`; + +const Cursor = styled.div` + position: relative; + height: 1.5px; + width: 2rem; + border-radius: 30px; + background-color: black; +`; From 5745d54ec4dd6684da50e03699bef4a2ed686e40 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:21:46 +0900 Subject: [PATCH 21/30] =?UTF-8?q?Feat(BuildingInfoToggle):=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EA=B2=B0=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - document에 클릭 또는 터치 이벤트를 추가해서 지도 위 오버레이(span)을 클릭할 경우 해당 건물 정보를 렌더링하는 컴포넌트를 반환하도록 구현 --- .../BuildingInfo/BuildingInfoToggle.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/pages/Map/components/BuildingInfo/BuildingInfoToggle.tsx diff --git a/src/pages/Map/components/BuildingInfo/BuildingInfoToggle.tsx b/src/pages/Map/components/BuildingInfo/BuildingInfoToggle.tsx new file mode 100644 index 00000000..afa54794 --- /dev/null +++ b/src/pages/Map/components/BuildingInfo/BuildingInfoToggle.tsx @@ -0,0 +1,31 @@ +import { eventType } from '@utils/map/regist-drag-event'; +import React, { useEffect, useState } from 'react'; + +import BuildingInfo from './BuildingInfo'; + +const BuildingInfoToggle = () => { + const [buildingNumber, setBuildingNumber] = useState(''); + + const unmountInfo = () => setBuildingNumber(''); + const isInfoMounted = buildingNumber !== ''; + + useEffect(() => { + const getNumber = (e: MouseEvent | TouchEvent) => { + if (!(e.target instanceof HTMLSpanElement) || isInfoMounted) return; + + setBuildingNumber(e.target.innerText); + }; + + document.addEventListener(eventType, getNumber); + + return () => { + document.removeEventListener(eventType, getNumber); + }; + }, [buildingNumber]); + + return isInfoMounted ? ( + + ) : null; +}; + +export default BuildingInfoToggle; From 0f85cac35fc1b166a44e843315fdeb1ba7395211 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:23:34 +0900 Subject: [PATCH 22/30] =?UTF-8?q?Feat(FloorInfo,=20Content):=20=EC=B8=B5?= =?UTF-8?q?=EB=B3=84=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EB=B3=B4=EC=97=AC?= =?UTF-8?q?=EC=A3=BC=EB=8A=94=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 지하, 지상, 옥탑으로 구분짓고 해당 정보를 렌더링 하도록 구현 --- .../Map/components/BuildingInfo/FloorInfo.tsx | 47 ++++++++++ .../BuildingInfo/FloorInfoContent.tsx | 94 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/pages/Map/components/BuildingInfo/FloorInfo.tsx create mode 100644 src/pages/Map/components/BuildingInfo/FloorInfoContent.tsx diff --git a/src/pages/Map/components/BuildingInfo/FloorInfo.tsx b/src/pages/Map/components/BuildingInfo/FloorInfo.tsx new file mode 100644 index 00000000..83e3ab67 --- /dev/null +++ b/src/pages/Map/components/BuildingInfo/FloorInfo.tsx @@ -0,0 +1,47 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import { TotalFloorInfo } from '@type/building-info'; +import React from 'react'; + +import FloorInfoContent from './FloorInfoContent'; + +interface FloorInfoProps { + floorInfo: TotalFloorInfo | Record; +} + +const FloorInfo = ({ floorInfo }: FloorInfoProps) => { + if (Object.keys(floorInfo).length === 0) { + return <>; + } + + const { basement, ground, rooftop } = floorInfo; + + return ( +
+ + 층별 안내 + + + + + +
+ ); +}; + +export default FloorInfo; + +const BoundaryLine = styled.hr` + height: 1px; + background-color: #ededed; + border: none; +`; diff --git a/src/pages/Map/components/BuildingInfo/FloorInfoContent.tsx b/src/pages/Map/components/BuildingInfo/FloorInfoContent.tsx new file mode 100644 index 00000000..5e222136 --- /dev/null +++ b/src/pages/Map/components/BuildingInfo/FloorInfoContent.tsx @@ -0,0 +1,94 @@ +import ToggleInfo from '@components/Common/ToggleInfo'; +import styled from '@emotion/styled'; +import { Floor, FloorInfo, Room } from '@type/building-info'; +import { formatFloorTitle } from '@utils/map/building-info'; +import React, { Fragment } from 'react'; + +interface FloorInfoContentProps { + floorType: Floor; + infoContent: FloorInfo | Record; +} + +const FloorInfoContent = ({ + floorType, + infoContent, +}: FloorInfoContentProps) => { + if (Object.keys(infoContent).length === 0) return <>; + + return ( + <> + {Object.keys(infoContent).map((floor, index) => ( + + ( + + {formatFloorTitle(floorType as Floor, floor)} + + )} + infoDesc={() => ( + + {(infoContent[floor] as Room[]).map( + ({ roomNumber, roomName }, dataIndex) => ( + + {roomNumber} + + {roomName} + + ), + )} + + )} + /> + + + ))} + + ); +}; + +export default FloorInfoContent; + +const FloorText = styled.span` + font-size: 1.3rem; + font-weight: bold; +`; + +const RoomInfoContainer = styled.section` + border: 1px solid #e5e5e5; + + & :last-child { + border-bottom: none; + } +`; + +const RoomInfo = styled.div` + padding: 0.6rem; + height: 2rem; + border-bottom: 1px solid #e5e5e5; + display: flex; + align-items: center; + font-size: 0.9rem; +`; + +const RoomNumber = styled.span` + width: 25%; + margin-right: 0.7rem; +`; + +const RoomName = styled.span` + width: 75%; + line-height: 1.4; +`; + +const Seperator = styled.div` + width: 1px; + height: 3.2rem; + background-color: #e5e5e5; + margin-right: 1rem; +`; + +const BoundaryLine = styled.hr` + height: 1px; + background-color: #ededed; + border: none; +`; From 4a5ed10e7652aae81d0a5189e8204afb2c40141f Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:25:29 +0900 Subject: [PATCH 23/30] =?UTF-8?q?Feat(InfoContent):=20=EA=B1=B4=EB=AC=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=EC=9D=98=20=EB=82=B4=EC=9A=A9=EC=9D=84=20?= =?UTF-8?q?=EB=B3=B4=EC=97=AC=EC=A3=BC=EB=8A=94=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 건물 이름, 사진, 길찾기, 건물 정보를 렌더링 - 현재 건물 정보 컴포넌트의 높이가 변할 때마다 BuildingInfo 컴포넌트가 리렌더링 되어, 자식 컴포넌트인 InfoContent 또한 리렌더링 되는데 이를 방지하기 위해서 memo 사용 --- .../components/BuildingInfo/InfoContent.tsx | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/pages/Map/components/BuildingInfo/InfoContent.tsx diff --git a/src/pages/Map/components/BuildingInfo/InfoContent.tsx b/src/pages/Map/components/BuildingInfo/InfoContent.tsx new file mode 100644 index 00000000..5f97d9c8 --- /dev/null +++ b/src/pages/Map/components/BuildingInfo/InfoContent.tsx @@ -0,0 +1,122 @@ +import Button from '@components/Common/Button'; +import Icon from '@components/Common/Icon'; +import Image from '@components/Common/Image'; +import Modal from '@components/Common/Modal'; +import { MODAL_BUTTON_MESSAGE } from '@constants/modal-messages'; +import TOAST_MESSAGES from '@constants/toast-message'; +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; +import useBuildingInfo from '@hooks/useBuildingInfo'; +import useModals from '@hooks/useModals'; +import useToasts from '@hooks/useToast'; +import useUserLocation from '@hooks/useUserLocation'; +import { THEME } from '@styles/ThemeProvider/theme'; +import { forrmatRoutingUrl } from '@utils/map/building-info'; +import { hasLocationPermission } from '@utils/map/user-location'; +import openLink from '@utils/router/openLink'; +import React, { CSSProperties } from 'react'; + +import FloorInfo from './FloorInfo'; + +interface InfoContentProps { + buildingNumber: string; +} + +const InfoContent = ({ buildingNumber }: InfoContentProps) => { + const userLocation = useUserLocation(); + const { openModal } = useModals(); + const { addToast } = useToasts(); + const { + floorInfo, + buildingInfo: { buildingName, imgPath, color, latlng }, + } = useBuildingInfo(buildingNumber); + + const buildingLabel = `${buildingNumber} ${buildingName}`; + + const handleRoutingModal = () => { + if (!hasLocationPermission(userLocation)) { + addToast(TOAST_MESSAGES.SHARE_LOCATION); + return; + } + + const openUrl = forrmatRoutingUrl(userLocation, latlng, buildingName); + + openModal( + + + + openLink(openUrl)} + /> + , + ); + }; + + return ( + + + + {buildingLabel} + + + + + + + ); +}; + +// TODO : memo를 사용한 경우와 그렇지 않은 경우 렌더링 속도 비교하기 +export default React.memo(InfoContent); + +const Wrapper = styled.section` + position: absolute; + overflow-y: scroll; + height: 100%; + width: 100%; + background-color: white; + cursor: move; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +`; + +const ContentContainer = styled.div` + position: relative; + padding: 40px 30px 0px 30px; +`; + +const ContentHeader = styled.header` + padding: 0px 0px 20px 0px; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + row-gap: 1.2rem; +`; + +const BuildingLabel = styled.span<{ color: CSSProperties['color'] }>` + color: ${({ color }) => color}; + font-size: 1.2rem; + font-weight: bold; +`; + +const ButtonText = styled.span` + color: ${THEME.TEXT.BLACK}; + font-size: 1rem; +`; From 59c3a5ddf93fc1faaccddd7d7bb2151421bb6848 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:26:23 +0900 Subject: [PATCH 24/30] =?UTF-8?q?Chore(change=20export=20path):=20?= =?UTF-8?q?=EC=9C=84=EB=8F=84,=20=EA=B2=BD=EB=8F=84=EB=A1=9C=20=EA=B1=B0?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EA=B3=84=EC=82=B0=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EC=9D=98=20=ED=8F=B4=EB=8D=94=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=9D=B4=EB=8F=99=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Map/handlers/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Map/handlers/index.ts b/src/pages/Map/handlers/index.ts index 1849389b..0175030a 100644 --- a/src/pages/Map/handlers/index.ts +++ b/src/pages/Map/handlers/index.ts @@ -1,2 +1 @@ -export { default as getHaversineDistance } from './distance'; export { default as handleMapBoundary } from './boundary'; From 1eba2eda1a1c01ef71d8085f1d63ebd5f9d1c5c7 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:30:43 +0900 Subject: [PATCH 25/30] =?UTF-8?q?Chore(add=20import=20path):=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=EB=A5=BC=20utils=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=A1=9C=20=EC=9D=B4=EB=8F=99=20=ED=9B=84=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useBuildingInfo.ts | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/hooks/useBuildingInfo.ts b/src/hooks/useBuildingInfo.ts index eb4a7e26..2c201879 100644 --- a/src/hooks/useBuildingInfo.ts +++ b/src/hooks/useBuildingInfo.ts @@ -1,7 +1,6 @@ import fetchBuildingInfo from '@apis/building-info/fetch-building-info'; -import { PKNU_BUILDINGS } from '@constants/pknu-map'; import { TotalFloorInfo } from '@type/building-info'; -import { BuildingType } from '@type/map'; +import { getBuildingInfo } from '@utils/map/building-info'; import { AxiosResponse } from 'axios'; import { CSSProperties, useEffect, useState } from 'react'; @@ -12,23 +11,6 @@ interface BuildingInfo { color: CSSProperties['color']; } -const getBuildingInfo = (buildingNumber: string) => { - const buildingTypes = Object.keys(PKNU_BUILDINGS) as BuildingType[]; - - for (const type of buildingTypes) { - for (const building of PKNU_BUILDINGS[type].buildings) { - if (building.buildingNumber !== buildingNumber) continue; - - return { - buildingCode: building.buildingCode, - buildingName: building.buildingName, - color: PKNU_BUILDINGS[type].activeColor, - latlng: building.latlng, - }; - } - } -}; - const useBuildingInfo = (buildingNumber: string) => { const [floorInfo, setFloorInfo] = useState( {} as TotalFloorInfo, From 9fd79ae5afccfbb58dd657fc8310f33080f45a39 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:31:09 +0900 Subject: [PATCH 26/30] =?UTF-8?q?Chore(change=20function=20name):=20?= =?UTF-8?q?=EA=B1=B4=EB=AC=BC=20=EA=B2=80=EC=83=89=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EB=A5=BC=20=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Map/components/MapHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Map/components/MapHeader.tsx b/src/pages/Map/components/MapHeader.tsx index 687ef749..21c3ce5e 100644 --- a/src/pages/Map/components/MapHeader.tsx +++ b/src/pages/Map/components/MapHeader.tsx @@ -8,7 +8,7 @@ import useOverlays from '@hooks/useOverlays'; import useToasts from '@hooks/useToast'; import { THEME } from '@styles/ThemeProvider/theme'; import { BuildingType, PKNUBuilding } from '@type/map'; -import { getBuildingInfo } from '@utils/map/building-info'; +import { getBuildingSearchResult } from '@utils/map/building-info'; import React, { useRef } from 'react'; const MapHeader = () => { @@ -36,7 +36,7 @@ const MapHeader = () => { return; } - const searchResult = getBuildingInfo(inputRef.current?.value); + const searchResult = getBuildingSearchResult(inputRef.current?.value); if (!searchResult) { addToast(MODAL_MESSAGE.ALERT.SEARCH_FAILED); return; From aa51c2bd1353823641a4553fe32f564bc7cf7796 Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:31:53 +0900 Subject: [PATCH 27/30] =?UTF-8?q?Feat(util=20function):=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=EC=99=80=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=EB=90=9C=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 건물 검색 결과, 건물 정보 확인, 길찾기 url, 층별 정보 문자열 --- src/utils/map/building-info.ts | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/utils/map/building-info.ts diff --git a/src/utils/map/building-info.ts b/src/utils/map/building-info.ts new file mode 100644 index 00000000..39c9771c --- /dev/null +++ b/src/utils/map/building-info.ts @@ -0,0 +1,65 @@ +import { PKNU_BUILDINGS } from '@constants/pknu-map'; +import { Floor } from '@type/building-info'; +import { BuildingType, Location } from '@type/map'; + +export const getBuildingSearchResult = ( + keyword: string, +): [BuildingType, number] | undefined => { + const formattedKeyword = keyword.replaceAll(' ', '').toUpperCase(); + + for (const buildingType of Object.keys(PKNU_BUILDINGS)) { + const index = PKNU_BUILDINGS[ + buildingType as BuildingType + ].buildings.findIndex( + (PKNU_BUILDING) => + PKNU_BUILDING.buildingName === formattedKeyword || + PKNU_BUILDING.buildingNumber === formattedKeyword, + ); + + if (index !== -1) return [buildingType as BuildingType, index]; + } + + return; +}; + +export const getBuildingInfo = (buildingNumber: string) => { + const buildingTypes = Object.keys(PKNU_BUILDINGS) as BuildingType[]; + + for (const type of buildingTypes) { + for (const building of PKNU_BUILDINGS[type].buildings) { + if (building.buildingNumber !== buildingNumber) continue; + + return { + buildingCode: building.buildingCode, + buildingName: building.buildingName, + color: PKNU_BUILDINGS[type].activeColor, + latlng: building.latlng, + }; + } + } +}; + +export const forrmatRoutingUrl = ( + userLocation: Location | null, + latlng: [number, number], + buildingName: string, +): string => { + if (!userLocation) return ''; + + const { LAT, LNG } = userLocation; + const [lat, lng] = latlng; + + const kakaoMapAppURL = `kakaomap://route?sp=${LAT},${LNG}&ep=${lat},${lng}`; + const kakaoMapWebURL = `https://map.kakao.com/link/from/현위치,${LAT},${LNG}/to/${buildingName},${lat},${lng}`; + const isKakaoMapInstalled = /KAKAOMAP/i.test(navigator.userAgent); + const openUrl = isKakaoMapInstalled ? kakaoMapAppURL : kakaoMapWebURL; + + return openUrl; +}; + +export const formatFloorTitle = (type: Floor, floor: string): string => { + if (type === 'basement') return `B${floor}F`; + if (type === 'ground') return `${floor}F`; + if (type === 'rooftop') return `R${floor}F`; + return ''; +}; From d9d872eda60a664225660c54ac6330b1413a4e1f Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:32:21 +0900 Subject: [PATCH 28/30] =?UTF-8?q?Feat(util=20function):=20=EA=B1=B4?= =?UTF-8?q?=EB=AC=BC=20=EC=A0=95=EB=B3=B4=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=93=9C=EB=9E=98=EA=B7=B8=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/map/regist-drag-event.ts | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/utils/map/regist-drag-event.ts diff --git a/src/utils/map/regist-drag-event.ts b/src/utils/map/regist-drag-event.ts new file mode 100644 index 00000000..b45d78de --- /dev/null +++ b/src/utils/map/regist-drag-event.ts @@ -0,0 +1,59 @@ +export const inrange = (v: number, min: number, max: number) => { + if (v < min) return min; + if (v > max) return max; + return v; +}; + +export const isTouchScreen = + typeof window !== 'undefined' && + window.matchMedia('(hover: none) and (pointer: coarse)').matches; + +export const eventType = isTouchScreen ? 'touchstart' : 'click'; + +export const registDragEvent = ( + onDragChange: (deltaY: number) => void, + stopPropagation?: boolean, +) => { + if (isTouchScreen) { + return { + onTouchStart: (touchEvent: React.TouchEvent) => { + if (stopPropagation) touchEvent.stopPropagation(); + + const touchMoveHandler = (moveEvent: TouchEvent) => { + if (moveEvent.cancelable) moveEvent.preventDefault(); + + const deltaY = + moveEvent.touches[0].screenY - touchEvent.touches[0].screenY; + onDragChange(deltaY); + }; + + const touchEndHandler = () => { + document.removeEventListener('touchmove', touchMoveHandler); + }; + + document.addEventListener('touchmove', touchMoveHandler, { + passive: false, + }); + document.addEventListener('touchend', touchEndHandler, { once: true }); + }, + }; + } + + return { + onMouseDown: (clickEvent: React.MouseEvent) => { + if (stopPropagation) clickEvent.stopPropagation(); + + const mouseMoveHandler = (moveEvent: MouseEvent) => { + const deltaY = moveEvent.screenY - clickEvent.screenY; + onDragChange(deltaY); + }; + + const mouseUpHandler = () => { + document.removeEventListener('mousemove', mouseMoveHandler); + }; + + document.addEventListener('mousemove', mouseMoveHandler); + document.addEventListener('mouseup', mouseUpHandler, { once: true }); + }, + }; +}; From 04ec8a67987036d0ac6c9b22765eaf445f8cd16c Mon Sep 17 00:00:00 2001 From: hwinkr Date: Sat, 27 Jan 2024 22:32:53 +0900 Subject: [PATCH 29/30] =?UTF-8?q?Feat(util=20function):=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=9C=84=EC=B9=98=EC=99=80=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=EB=90=9C=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 위치 정보를 허용하는지, 사용자가 학교 내부에 있는지 확인하는 함수 구현 --- src/utils/map/user-location.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/utils/map/user-location.ts diff --git a/src/utils/map/user-location.ts b/src/utils/map/user-location.ts new file mode 100644 index 00000000..a6682e4f --- /dev/null +++ b/src/utils/map/user-location.ts @@ -0,0 +1,34 @@ +import { NO_PROVIDE_LOCATION, PKNU_MAP_CENTER } from '@constants/pknu-map'; +import { Location } from '@type/map'; + +const degreeToRadian = (deg: number) => deg * (Math.PI / 180); + +const getHaversineDistance = (lat: number, lng: number) => { + const R = 6371000; + + const dLat = degreeToRadian(PKNU_MAP_CENTER.LAT - lat); + const dLon = degreeToRadian(PKNU_MAP_CENTER.LNG - lng); + + const halfSideLength = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(degreeToRadian(PKNU_MAP_CENTER.LAT)) * + Math.cos(degreeToRadian(lat)) * + Math.sin(dLon / 2) * + Math.sin(dLon / 2); + + const angularDistance = + 2 * Math.atan2(Math.sqrt(halfSideLength), Math.sqrt(1 - halfSideLength)); + + return R * angularDistance; +}; + +export const hasLocationPermission = (location: Location | null) => { + return ( + location && JSON.stringify(location) !== JSON.stringify(NO_PROVIDE_LOCATION) + ); +}; + +export const isUserInShcool = (lat: number, lng: number) => { + const maxDistance = 450; + return getHaversineDistance(lat, lng) <= maxDistance; +}; From cbf5d411181807fe5865eb682ad69bf51ead73ca Mon Sep 17 00:00:00 2001 From: hwinkr Date: Mon, 29 Jan 2024 17:11:36 +0900 Subject: [PATCH 30/30] =?UTF-8?q?Chore(change=20node=20version):=20?= =?UTF-8?q?=EB=85=B8=EB=93=9C=20=EB=B2=84=EC=A0=BC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfile.dev b/dockerfile.dev index de478ac0..7228ea0f 100644 --- a/dockerfile.dev +++ b/dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:16 +FROM node:18 WORKDIR /usr/src/app COPY package.json ./ RUN yarn