diff --git a/frontend/package.json b/frontend/package.json
index 8bc7b9b8..d5fe6de4 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -57,6 +57,7 @@
"typescript": "~5.6.2",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
+ "vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.1"
},
"eslintConfig": {
diff --git a/frontend/public/assets/icons/Assistant Navigation Icon.svg b/frontend/public/assets/icons/Assistant Navigation Icon.svg
new file mode 100644
index 00000000..f84efc86
--- /dev/null
+++ b/frontend/public/assets/icons/Assistant Navigation Icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/assets/icons/Person Pin Icon.svg b/frontend/public/assets/icons/Person Pin Icon.svg
new file mode 100644
index 00000000..94948f20
--- /dev/null
+++ b/frontend/public/assets/icons/Person Pin Icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/assets/icons/flag.svg b/frontend/public/assets/icons/flag.svg
new file mode 100644
index 00000000..2fbb36c2
--- /dev/null
+++ b/frontend/public/assets/icons/flag.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/assets/icons/location_on.svg b/frontend/public/assets/icons/location_on.svg
new file mode 100644
index 00000000..a6ce005d
--- /dev/null
+++ b/frontend/public/assets/icons/location_on.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/api/channel.api.ts b/frontend/src/api/channel.api.ts
index de9d6fe4..1907acdc 100644
--- a/frontend/src/api/channel.api.ts
+++ b/frontend/src/api/channel.api.ts
@@ -2,6 +2,7 @@ import { ResponseDto } from '@/api/dto/response.dto.ts';
import {
createChannelReqEntity,
createChannelResEntity,
+ getChannelResEntity,
getUserChannelsResEntity,
} from '@/api/dto/channel.dto.ts';
import { getApiClient } from '@/api/client.api.ts';
@@ -56,9 +57,9 @@ export const getUserChannels = (userId: string): Promise> => {
+export const getChannelInfo = (userId: string): Promise> => {
const promiseFn = (
- fnResolve: (value: ResponseDto) => void,
+ fnResolve: (value: ResponseDto) => void,
fnReject: (reason?: any) => void,
) => {
const apiClient = getApiClient();
@@ -69,7 +70,7 @@ export const getChannelInfo = (userId: string): Promise(res.data));
+ fnResolve(new ResponseDto(res.data));
}
})
.catch(err => {
diff --git a/frontend/src/api/dto/channel.dto.ts b/frontend/src/api/dto/channel.dto.ts
index 58150eeb..d07fb3a5 100644
--- a/frontend/src/api/dto/channel.dto.ts
+++ b/frontend/src/api/dto/channel.dto.ts
@@ -9,6 +9,8 @@ export class guestMarkerStyleEntity {
}
export class guestEntity {
+ id: string | undefined;
+
name: string | undefined;
start_location: locationEntity | undefined;
@@ -57,3 +59,13 @@ export class channelListEntity {
export class getUserChannelsResEntity {
channels: channelListEntity[] | undefined;
}
+
+export class getChannelResEntity {
+ id: string | undefined;
+
+ name: string | undefined;
+
+ host_id: string | undefined;
+
+ guests: guestEntity[] | undefined;
+}
diff --git a/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx b/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx
index 45596aeb..f65c1d9e 100644
--- a/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx
+++ b/frontend/src/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx
@@ -36,6 +36,7 @@ export const MapCanvasForView = ({
endImageRef.current = new Image();
endImageRef.current.src = endmarker;
+ console.log(guests);
}, []);
useEffect(() => {
diff --git a/frontend/src/component/common/dropdown/DropdownItem.tsx b/frontend/src/component/common/dropdown/DropdownItem.tsx
index 46897929..92378d15 100644
--- a/frontend/src/component/common/dropdown/DropdownItem.tsx
+++ b/frontend/src/component/common/dropdown/DropdownItem.tsx
@@ -1,40 +1,27 @@
import { ReactNode } from 'react';
import classNames from 'classnames';
+import { useLocation, useNavigate } from 'react-router-dom';
interface IDropdownItemProps {
- /** 드롭다운 아이템 내용 */
children: ReactNode;
- /** 버튼 클릭 시 실행할 함수 */
- onClick?: React.MouseEventHandler;
+ path?: string;
className?: string;
}
-/**
- * 드롭다운 메뉴의 아이템 컴포넌트입니다.
- *
- * @param {ReactNode} children - 드롭다운 아이템 내용
- * @param {React.MouseEventHandler} onClick - 버튼 클릭 시 실행할 함수
- * @return {JSX.Element} 드롭다운 아이템 컴포넌트
- *
- * @remarks
- * - 드롭다운 아이템 컴포넌트는 드롭다운 메뉴 내부에서 사용되어야 합니다.
- * - 드롭다운 아이템 컴포넌트는 드롭다운 메뉴의 아이템 역할을 수행합니다.
- * - 드롭다운 아이템 컴포넌트는 버튼 역할을 수행합니다.
- * - 드롭다운 아이템 컴포넌트는 클릭 시 onClick 함수를 실행합니다.
- *
- * @example
- * ```tsx
- * 아이템
- * ```
- */
-
export const DropdownItem = (props: IDropdownItemProps) => {
+ const location = useLocation();
+ const navigate = useNavigate();
+
+ const handleClick = () => {
+ navigate(`${location.pathname}/${props.path}`);
+ };
+
return (
diff --git a/frontend/src/component/header/Header.tsx b/frontend/src/component/header/Header.tsx
index 61d68d5c..136b8691 100644
--- a/frontend/src/component/header/Header.tsx
+++ b/frontend/src/component/header/Header.tsx
@@ -5,6 +5,7 @@ import { HeaderTitle } from '@/component/header/HeaderTitle.tsx';
import { HeaderMainLayout } from '@/component/header/HeaderMainLayout.tsx';
import { HeaderSubtitle } from '@/component/header/HeaderSubtitle.tsx';
import classNames from 'classnames';
+import { IGuestData } from '@/types/channel.types.ts';
interface IHeaderProps {
children: ReactNode;
@@ -16,7 +17,7 @@ export interface IHeaderOption {
rightButton?: string;
title?: ReactNode;
subtitle?: string;
- items?: string[];
+ items?: string[] | IGuestData[];
}
export const Header = (props: IHeaderProps) => {
diff --git a/frontend/src/component/header/HeaderDropdown.tsx b/frontend/src/component/header/HeaderDropdown.tsx
index 60f20c97..a467143e 100644
--- a/frontend/src/component/header/HeaderDropdown.tsx
+++ b/frontend/src/component/header/HeaderDropdown.tsx
@@ -1,35 +1,26 @@
import { Dropdown } from '@/component/common/dropdown/Dropdown.tsx';
import { MdMenu, MdLocationOn } from 'react-icons/md';
import { DropdownItem } from '@/component/common/dropdown/DropdownItem.tsx';
-import classNames from 'classnames';
+import { IGuestData } from '@/types/channel.types.ts';
interface IDropdownContainerProps {
- items: string[];
+ items: IGuestData[];
}
export const HeaderDropdown = (props: IDropdownContainerProps) => {
- // TODO: 하드코딩된 자료 말고 마커 색상 가져오기
- const textMarkerUser = [
- 'text-marker-user1',
- 'text-marker-user2',
- 'text-marker-user3',
- 'text-marker-user4',
- 'text-marker-user5',
- ];
-
const DropdownItems = () => {
- const Items = props.items.map((e, i) => {
+ const Items = props.items.map(guestData => {
return (
-
- {e}
-
+
+ {guestData.name} 위치
+
);
});
if (Items.length > 1) {
Items.push(
-
+
모두 보기
,
);
diff --git a/frontend/src/component/layout/header/LayoutHeaderProvider.tsx b/frontend/src/component/layout/header/LayoutHeaderProvider.tsx
index 36547207..cd096314 100644
--- a/frontend/src/component/layout/header/LayoutHeaderProvider.tsx
+++ b/frontend/src/component/layout/header/LayoutHeaderProvider.tsx
@@ -1,5 +1,6 @@
import { ReactNode, createContext, useReducer, useMemo, useCallback } from 'react';
import { IHeaderOption } from '@/component/header/Header.tsx';
+import { IGuestData } from '@/types/channel.types.ts';
interface ILayoutHeaderProviderProps {
children: ReactNode;
@@ -11,7 +12,7 @@ interface IHeaderOptionContext {
setSubTitle: (subtitle: string) => void;
setLeftButton: (leftButton: string) => void;
setRightButton: (rightButton: string) => void;
- setItems: (items: string[]) => void;
+ setItems: (items: IGuestData[]) => void;
resetHeaderContext: () => void;
}
@@ -38,7 +39,7 @@ type Action =
| { type: 'SET_SUBTITLE'; payload: string }
| { type: 'SET_LEFT_BUTTON'; payload: string }
| { type: 'SET_RIGHT_BUTTON'; payload: string }
- | { type: 'SET_ITEMS'; payload: string[] };
+ | { type: 'SET_ITEMS'; payload: IGuestData[] };
const headerReducer = (state: IHeaderOption, action: Action): IHeaderOption => {
switch (action.type) {
@@ -76,7 +77,7 @@ export const LayoutHeaderProvider = (props: ILayoutHeaderProviderProps) => {
dispatch({ type: 'SET_RIGHT_BUTTON', payload: rightButton });
}, []);
- const setItems = useCallback((items: string[]) => {
+ const setItems = useCallback((items: IGuestData[]) => {
dispatch({ type: 'SET_ITEMS', payload: items });
}, []);
diff --git a/frontend/src/component/userMarker/UserMarker.tsx b/frontend/src/component/userMarker/UserMarker.tsx
new file mode 100644
index 00000000..2aeb209b
--- /dev/null
+++ b/frontend/src/component/userMarker/UserMarker.tsx
@@ -0,0 +1,21 @@
+import { MdLocationOn } from 'react-icons/md';
+import { IGuestData } from '@/types/channel.types.ts';
+
+interface IUserMarkerProps {
+ userData: IGuestData[];
+}
+
+export const UserMarker = (props: IUserMarkerProps) => {
+ return (
+
+
+ {props.userData.map(data => (
+ -
+
+ {data.name}
+
+ ))}
+
+
+ );
+};
diff --git a/frontend/src/lib/types/canvasInterface.ts b/frontend/src/lib/types/canvasInterface.ts
index 924592a5..15445017 100644
--- a/frontend/src/lib/types/canvasInterface.ts
+++ b/frontend/src/lib/types/canvasInterface.ts
@@ -25,12 +25,17 @@ export interface IOtherLiveLocations {
token: string;
}
+export interface IMarkerStyle {
+ color: string;
+}
+
export interface IGuestDataInMapProps {
- guestName: string;
- guestUUID: string;
+ id: string;
+ name: string;
startPoint: IPoint;
endPoint: IPoint;
paths: IPoint[];
+ markerStyle: IMarkerStyle;
}
export interface IMapCanvasViewProps {
diff --git a/frontend/src/pages/HostView.tsx b/frontend/src/pages/HostView.tsx
index ffe2e09d..c1c2d36e 100644
--- a/frontend/src/pages/HostView.tsx
+++ b/frontend/src/pages/HostView.tsx
@@ -1,48 +1,109 @@
-// import { HeaderContext } from '@/component/layout/header/LayoutHeaderProvider';
-// import { useContext, useEffect, useState } from 'react';
-// import { IUserChannelInfo } from '@/types/channel.types.ts';
-// import { getChannelInfo } from '@/api/channel.api.ts';
-// import { useLocation } from 'react-router-dom';
-// import { CanvasWithMap } from '@/component/canvas/CanvasWithMap.tsx';
-
-// export const HostView = () => {
-// const [userChannels, setUserChannels] = useState();
-// const [userNames, setUserNames] = useState(['사용자 1']);
-
-// const headerContext = useContext(HeaderContext);
-
-// const location = useLocation();
-
-// const fetchChannelInfo = (id: string) => {
-// getChannelInfo(id)
-// .then(res => {
-// if (res.data) setUserChannels(res.data);
-// })
-// .catch((error: any) => {
-// console.error(error);
-// });
-// };
-
-// useEffect(() => {
-// headerContext.setRightButton('dropdown');
-// headerContext.setLeftButton('back');
-// headerContext.setItems(['1']);
-
-// fetchChannelInfo(location.pathname.split('/')[2]);
-// }, []);
-
-// useEffect(() => {
-// if (userChannels?.guests) {
-// const names = userChannels.guests.filter(Boolean).map(guest => guest.name!);
-// setUserNames(names);
-// }
-// }, [userChannels]);
-
-// useEffect(() => {
-// headerContext.setItems(userNames);
-// }, [userNames]);
-
-// // TODO: geoCoding API를 이용해서 현재 위치나 시작위치를 기반으로 자동 좌표 설정 구현 (현재: 하드코딩)
-// return ;
-// };
-export const HostView = () => <>Hello>;
+import { HeaderContext } from '@/component/layout/header/LayoutHeaderProvider';
+import { ReactNode, useContext, useEffect, useState } from 'react';
+import { IGuest, IChannelInfo, IGuestData } from '@/types/channel.types.ts';
+import { getChannelInfo } from '@/api/channel.api.ts';
+import { useLocation } from 'react-router-dom';
+import { MapCanvasForView } from '@/component/canvasWithMap/canvasWithMapForView/MapCanvasForView.tsx';
+import { IGuestDataInMapProps, IPoint } from '@/lib/types/canvasInterface.ts';
+import { getChannelResEntity, guestEntity } from '@/api/dto/channel.dto.ts';
+import { UserMarker } from '@/component/userMarker/UserMarker.tsx';
+
+export const HostView = () => {
+ const [channelInfo, setChannelInfo] = useState();
+ const [guestData, setGuestData] = useState([]);
+ const [mapProps, setMapProps] = useState([]);
+ const [component, setComponent] = useState();
+
+ const headerContext = useContext(HeaderContext);
+
+ const location = useLocation();
+
+ const transformTypeGuestEntityToIGuest = (props: guestEntity): IGuest => {
+ return {
+ id: props.id ?? '',
+ name: props.name ?? '',
+ startPoint: {
+ lat: props.start_location?.lat ?? 0,
+ lng: props.start_location?.lng ?? 0,
+ },
+ endPoint: {
+ lat: props.end_location?.lat ?? 0,
+ lng: props.end_location?.lng ?? 0,
+ },
+ paths: (props.path as IPoint[]) ?? [],
+ markerStyle: {
+ color: props.marker_style?.color ?? '',
+ },
+ };
+ };
+
+ const transformTypeFromResToInfo = (props: getChannelResEntity): IChannelInfo => {
+ const guests = props.guests?.map(guest => transformTypeGuestEntityToIGuest(guest)) ?? [];
+
+ return {
+ name: props.name ?? '',
+ hostId: props.host_id ?? '',
+ channelId: props.id ?? '',
+ guests,
+ };
+ };
+
+ const fetchChannelInfo = (userId: string) => {
+ getChannelInfo(userId)
+ .then(res => {
+ if (!res.data) throw new Error('🚀 Fetch Error: responsed undefined');
+ const transfromedData = transformTypeFromResToInfo(res.data);
+ setChannelInfo(transfromedData);
+ })
+ .catch((error: any) => {
+ console.error(error);
+ });
+ };
+
+ useEffect(() => {
+ headerContext.setRightButton('dropdown');
+ headerContext.setLeftButton('back');
+ headerContext.setItems([{ name: '사용자 1', id: '1', markerStyle: { color: '#000' } }]);
+
+ fetchChannelInfo(location.pathname.split('/')[2]);
+ }, []);
+
+ useEffect(() => {
+ if (channelInfo?.guests) {
+ const data: IGuestData[] = channelInfo.guests.filter(Boolean).map(guest => ({
+ name: guest.name,
+ markerStyle: guest.markerStyle,
+ id: guest.id,
+ }));
+ setGuestData(data);
+ channelInfo.guests?.map(guest =>
+ setMapProps(prev => [...prev, guest as IGuestDataInMapProps]),
+ );
+ }
+ }, [channelInfo]);
+
+ useEffect(() => {
+ headerContext.setItems(guestData);
+ }, [guestData]);
+
+ useEffect(() => {
+ if (mapProps.length > 1) {
+ setComponent(
+ ,
+ );
+ }
+ }, [mapProps]);
+
+ return (
+
+
+ {component}
+
+ );
+};
diff --git a/frontend/src/types/channel.types.ts b/frontend/src/types/channel.types.ts
index c96961e0..c3e63a31 100644
--- a/frontend/src/types/channel.types.ts
+++ b/frontend/src/types/channel.types.ts
@@ -1,22 +1,30 @@
-export interface ILocation {
- lat: number | undefined;
- lng: number | undefined;
+export interface IPoint {
+ lat: number;
+ lng: number;
}
export interface IGuestMarkerStyle {
- color: string | undefined;
+ color: string;
}
export interface IGuest {
- name: string | undefined;
- start_location: ILocation | undefined;
- end_location: ILocation | undefined;
- path: ILocation[] | undefined;
- marker_style: IGuestMarkerStyle | undefined;
+ id: string;
+ name: string;
+ startPoint: IPoint;
+ endPoint: IPoint;
+ paths: IPoint[];
+ markerStyle: IGuestMarkerStyle;
}
-export interface IUserChannelInfo {
- host_id: string | undefined;
- name: string | undefined;
- guests: IGuest[] | undefined;
+export interface IGuestData {
+ id: string;
+ name: string;
+ markerStyle: IGuestMarkerStyle;
+}
+
+export interface IChannelInfo {
+ channelId: string;
+ hostId: string;
+ name: string;
+ guests: IGuest[];
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b804b6a9..662a3794 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -311,6 +311,9 @@ importers:
vite:
specifier: ^5.4.10
version: 5.4.11(@types/node@22.9.0)(terser@5.36.0)
+ vite-plugin-svgr:
+ specifier: ^4.3.0
+ version: 4.3.0(rollup@4.26.0)(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(terser@5.36.0))
vite-tsconfig-paths:
specifier: ^5.1.1
version: 5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(terser@5.36.0))
@@ -7633,6 +7636,11 @@ packages:
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
+ vite-plugin-svgr@4.3.0:
+ resolution: {integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==}
+ peerDependencies:
+ vite: '>=2.6.0'
+
vite-tsconfig-paths@5.1.2:
resolution: {integrity: sha512-gEIbKfJzSEv0yR3XS2QEocKetONoWkbROj6hGx0FHM18qKUojhvcokQsxQx5nMkelZq2n37zbSGCJn+FSODSjA==}
peerDependencies:
@@ -17078,6 +17086,17 @@ snapshots:
- supports-color
- terser
+ vite-plugin-svgr@4.3.0(rollup@4.26.0)(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(terser@5.36.0)):
+ dependencies:
+ '@rollup/pluginutils': 5.1.3(rollup@4.26.0)
+ '@svgr/core': 8.1.0(typescript@5.6.3)
+ '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3))
+ vite: 5.4.11(@types/node@22.9.0)(terser@5.36.0)
+ transitivePeerDependencies:
+ - rollup
+ - supports-color
+ - typescript
+
vite-tsconfig-paths@5.1.2(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(terser@5.36.0)):
dependencies:
debug: 4.3.7