Skip to content

Commit

Permalink
Merge pull request #245 from boostcampwm-2024/feature/fe/4week-dev-test
Browse files Browse the repository at this point in the history
[FE][Feat] Main.tsx ํ™”๋ฉด์—์„œ 4์ฃผ์ฐจ ๋ฐ๋ชจ๋ฅผ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ํ˜„์žฌ ์œ„์น˜ ๋ฐ›์•„์˜ค๋Š” ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • Loading branch information
happyhyep authored Nov 21, 2024
2 parents 8d6f0a2 + 2125356 commit 9c93848
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 8 deletions.
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export default [
'no-underscore-dangle': 'warn',
'no-undef': 'off',
'max-classes-per-file': 'off',
'no-bitwise': 'off',
'no-plusplus': 'off',
'import/extensions': [
'error',
'always',
Expand Down
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"lint": "pnpm lint-staged",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"uuid": "^11.0.3"
},
"dependencies": {
"@fontsource/pretendard": "^5.1.0",
Expand Down Expand Up @@ -63,4 +64,4 @@
"plugin:storybook/recommended"
]
}
}
}
82 changes: 80 additions & 2 deletions frontend/src/component/maps/NaverMapSample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,33 @@ export interface INaverMapOptions {
lng: number;
zoom?: number;
}
interface INaverMapProps extends INaverMapOptions {
otherLocations?: Array<{ location: { lat: number; lng: number }; token: string }>;
}

export const NaverMap = (props: INaverMapOptions) => {
export const NaverMap = (props: INaverMapProps) => {
const mapRef = useRef<HTMLDivElement>(null);
const mapInstanceRef = useRef<naver.maps.Map | null>(null);
const markersRef = useRef<Map<string, naver.maps.Marker>>(new Map());
const userMarkerRef = useRef<naver.maps.Marker | null>(null);

// TODO: ์‚ฌ์šฉ์ž ์ˆœ์„œ ๋ณ„๋กœ ์ƒ‰์ƒ ์ •ํ•˜๊ธฐ (util ํ•จ์ˆ˜๋กœ ๋นผ๊ธฐ)
const getMarkerColor = (token: string) => {
// ๋ฌธ์ž์—ด ํ•ด์‹ฑ์„ ํ†ตํ•ด ๊ณ ์œ  ์ˆซ์ž ์ƒ์„ฑ
let hash = 0;
for (let i = 0; i < token.length; i++) {
hash = token.charCodeAt(i) + ((hash << 5) - hash);
}

// ํ•ด์‹œ ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ RGB ๊ฐ’ ์ƒ์„ฑ
const r = (hash >> 16) & 0xff;
const g = (hash >> 8) & 0xff;
const b = hash & 0xff;

// RGB๋ฅผ HEX ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜
return `rgb(${r}, ${g}, ${b})`;
};

const mapOptions: INaverMapOptions = {
lat: props.lat,
lng: props.lng,
Expand All @@ -17,9 +41,63 @@ export const NaverMap = (props: INaverMapOptions) => {

useEffect(() => {
if (mapRef.current) {
setNaverMapSync(mapRef.current, mapOptions);
const map = setNaverMapSync(mapRef.current, mapOptions);
mapInstanceRef.current = map;
}
}, []);

useEffect(() => {
if (mapInstanceRef.current && props.otherLocations) {
const map = mapInstanceRef.current;

// ์—…๋ฐ์ดํŠธ๋œ ๋งˆ์ปค ๊ด€๋ฆฌ
const existingMarkers = markersRef.current;

props.otherLocations.forEach(({ location, token }) => {
const markerColor = getMarkerColor(token);
if (existingMarkers.has(token)) {
// ๊ธฐ์กด ๋งˆ์ปค ์œ„์น˜ ์—…๋ฐ์ดํŠธ
const marker = existingMarkers.get(token);
marker?.setPosition(new naver.maps.LatLng(location.lat, location.lng));
} else {
const newMarker = new naver.maps.Marker({
position: new naver.maps.LatLng(location.lat, location.lng),
map,
icon: {
content: `<div style="background-color: ${markerColor}; width: 20px; height: 20px; border-radius: 50%;"></div>`,
anchor: new naver.maps.Point(10, 10),
},
});

existingMarkers.set(token, newMarker);
}
});

// ์‚ญ์ œ๋œ ๋งˆ์ปค ์ œ๊ฑฐ
existingMarkers.forEach((marker, token) => {
if (!props.otherLocations?.some(loc => loc.token === token)) {
marker.setMap(null);
existingMarkers.delete(token);
}
});
}
}, [props.otherLocations]);

// ํ˜„์žฌ ์œ„์น˜ ๋งˆ์ปค ์ถ”๊ฐ€ ๋ฐ ์—…๋ฐ์ดํŠธ
useEffect(() => {
if (mapInstanceRef.current) {
const map = mapInstanceRef.current;
// ํ˜„์žฌ ์œ„์น˜ ๋งˆ์ปค ์ƒ์„ฑ
userMarkerRef.current = new naver.maps.Marker({
position: new naver.maps.LatLng(props.lat, props.lng),
map,
icon: {
content: `<div style="background-color: blue; width: 25px; height: 25px; border-radius: 50%; border: 2px solid white;"></div>`,
anchor: new naver.maps.Point(12.5, 12.5),
},
});
}
}, [props.lat, props.lng]);

return <section ref={mapRef} className="h-full w-full" />;
};
5 changes: 4 additions & 1 deletion frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
export const KEYS = {
LOGIN_USER: 'LUT',
LOGIN_TOKEN: 'LUT_TK',
BROWSER_TOKEN: 'BRW_TK',
};

export const AppConfig = {
/**
* API SERVER
*/
API_SERVER: 'http://223.130.151.43:3001/api',
API_SERVER: 'https://ddara.kro.kr:3001/api',
// API_SERVER: 'http://localhost:3001/api',
SOCKET_SERVER: 'wss://ddara.kro.kr/ws',
// SOCKET_SERVER: 'ws://localhost:3001',

/**
* ETC
Expand Down
51 changes: 48 additions & 3 deletions frontend/src/pages/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, useContext, useEffect } from 'react';
import { Fragment, useContext, useEffect, useState } from 'react';
import { getUserLocation } from '@/hooks/getUserLocation';
import { BottomSheet } from '@/component/bottomsheet/BottomSheet';
import { Content } from '@/component/content/Content';
Expand All @@ -7,6 +7,9 @@ import { FooterContext } from '@/component/layout/footer/LayoutFooterProvider';
import { useNavigate } from 'react-router-dom';
import { NaverMap } from '@/component/maps/NaverMapSample.tsx';
import { buttonActiveType } from '@/component/layout/enumTypes';
import { loadLocalData, saveLocalData } from '@/utils/common/manageLocalData.ts';
import { AppConfig } from '@/constants.ts';
import { v4 as uuidv4 } from 'uuid';

const contentData = [
{
Expand Down Expand Up @@ -37,9 +40,44 @@ export const Main = () => {
useContext(FooterContext);
const { lat, lng, error } = getUserLocation();
const navigate = useNavigate();
const MIN_HEIGHT = 0.2;
const [otherLocations, setOtherLocations] = useState<any[]>([]);
const MIN_HEIGHT = 0.5;
const MAX_HEIGHT = 0.8;

// eslint-disable-next-line consistent-return
useEffect(() => {
if (lat && lng) {
if (!loadLocalData(AppConfig.KEYS.BROWSER_TOKEN)) {
const token = uuidv4();
saveLocalData(AppConfig.KEYS.BROWSER_TOKEN, token);
}
const token = loadLocalData(AppConfig.KEYS.BROWSER_TOKEN);
const ws = new WebSocket(`${AppConfig.SOCKET_SERVER}/?token=${token}`);

// ์ดˆ๊ธฐ ์œ„์น˜ ์ „์†ก
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'location', location: { lat, lng } }));
};

ws.onmessage = event => {
const data = JSON.parse(event.data);

if (data.type === 'init') {
// ๊ธฐ์กด ํด๋ผ์ด์–ธํŠธ๋“ค์˜ ์œ„์น˜ ์ดˆ๊ธฐํ™”
setOtherLocations(data.clients);
} else if (data.type === 'location' && data.token !== token) {
// ์ƒˆ๋กœ ๋“ค์–ด์˜จ ์œ„์น˜ ์—…๋ฐ์ดํŠธ
setOtherLocations(prev =>
prev.some(loc => loc.token === data.token)
? prev.map(loc => (loc.token === data.token ? data : loc))
: [...prev, data],
);
}
};
return () => ws.close();
}
}, [lat, lng]);

const goToAddChannel = () => {
navigate('/add-channel');
};
Expand All @@ -63,8 +101,15 @@ export const Main = () => {
height: `calc(100% - ${MIN_HEIGHT * 100}%)`,
}}
>
{/* eslint-disable-next-line no-nested-ternary */}
{lat && lng ? (
<NaverMap lat={lat} lng={lng} zoom={20} />
otherLocations ? (
<NaverMap otherLocations={otherLocations} lat={lat} lng={lng} />
) : (
<section className="flex h-full items-center justify-center">
Loading map data...
</section>
)
) : (
<section className="flex h-full items-center justify-center">
{error ? `Error: ${error}` : 'Loading'}
Expand Down

0 comments on commit 9c93848

Please sign in to comment.