Skip to content

Commit

Permalink
Merge pull request #314 from GDSC-PKNU-Official/feat/#313
Browse files Browse the repository at this point in the history
Feat/#313: 건물 정보 UI 구현
  • Loading branch information
hwinkr authored Feb 2, 2024
2 parents 2e85813 + cbf5d41 commit 97ba73b
Show file tree
Hide file tree
Showing 35 changed files with 800 additions and 132 deletions.
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

0 comments on commit 97ba73b

Please sign in to comment.