-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 메인 지도, 바텀시트 추가 #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 32 commits
d5051cd
8a27e9d
df39ae7
4e38a98
b310a7c
fff3e0b
7486164
39b0b1a
331438e
1ac9e5d
bab7c9e
ddaa2ef
a9b0bb7
88fad06
953291e
a19a0a4
7c73cdd
f739fcb
bd88d8f
ff32029
2c210cb
8bb28b3
c72bca3
8df835e
79b1c1e
c712c45
b8bdf76
34df7ca
0db583a
a13054f
2016229
c44786e
3f7b8b2
75c4446
f21d824
5f60db1
03479c8
bafd712
acdd243
54c2c00
825b2a8
8b50003
032926a
bad5d4a
fa9c484
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| import type { AlbumWithPhotosResponse, PhotoListResponse } from '@repo/api-client'; | ||
| import type { Album, AlbumDetailData } from '@/types/album.type'; | ||
| import type { MapPin } from '@/types/map.type'; | ||
|
|
||
| const center = { latitude: 37.5665, longitude: 126.978 }; | ||
|
|
||
| const photoListResponseMock: PhotoListResponse = { | ||
| albums: [ | ||
| { | ||
| id: 1, | ||
| title: '한강 라이딩', | ||
| photoCount: 5, | ||
| thumbnailUrl: 'https://picsum.photos/id/1018/300/300', | ||
| photos: [ | ||
| { | ||
| id: 101, | ||
| url: 'https://picsum.photos/id/1018/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.003, | ||
| longitude: center.longitude + 0.008, | ||
| }, | ||
| }, | ||
| { | ||
| id: 102, | ||
| url: 'https://picsum.photos/id/1020/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.006, | ||
| longitude: center.longitude + 0.004, | ||
| }, | ||
| }, | ||
| { | ||
| id: 103, | ||
| url: 'https://picsum.photos/id/1024/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.004, | ||
| longitude: center.longitude + 0.002, | ||
| }, | ||
| }, | ||
| { | ||
| id: 104, | ||
| url: 'https://picsum.photos/id/1027/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.002, | ||
| longitude: center.longitude - 0.006, | ||
| }, | ||
| }, | ||
| { | ||
| id: 105, | ||
| url: 'https://picsum.photos/id/1035/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.004, | ||
| longitude: center.longitude - 0.004, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 2, | ||
| title: '카페 투어', | ||
| photoCount: 4, | ||
| thumbnailUrl: 'https://picsum.photos/id/1043/300/300', | ||
| photos: [ | ||
| { | ||
| id: 201, | ||
| url: 'https://picsum.photos/id/1043/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.012, | ||
| longitude: center.longitude - 0.002, | ||
| }, | ||
| }, | ||
| { | ||
| id: 202, | ||
| url: 'https://picsum.photos/id/1050/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.009, | ||
| longitude: center.longitude - 0.007, | ||
| }, | ||
| }, | ||
| { | ||
| id: 203, | ||
| url: 'https://picsum.photos/id/1052/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.007, | ||
| longitude: center.longitude - 0.011, | ||
| }, | ||
| }, | ||
| { | ||
| id: 204, | ||
| url: 'https://picsum.photos/id/1060/300/300', | ||
| location: { | ||
| latitude: center.latitude + 0.011, | ||
| longitude: center.longitude - 0.009, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 3, | ||
| title: '야경 산책', | ||
| photoCount: 6, | ||
| thumbnailUrl: 'https://picsum.photos/id/1063/300/300', | ||
| photos: [ | ||
| { | ||
| id: 301, | ||
| url: 'https://picsum.photos/id/1063/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.008, | ||
| longitude: center.longitude + 0.01, | ||
| }, | ||
| }, | ||
| { | ||
| id: 302, | ||
| url: 'https://picsum.photos/id/1067/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.01, | ||
| longitude: center.longitude + 0.014, | ||
| }, | ||
| }, | ||
| { | ||
| id: 303, | ||
| url: 'https://picsum.photos/id/1070/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.012, | ||
| longitude: center.longitude + 0.008, | ||
| }, | ||
| }, | ||
| { | ||
| id: 304, | ||
| url: 'https://picsum.photos/id/1074/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.006, | ||
| longitude: center.longitude + 0.004, | ||
| }, | ||
| }, | ||
| { | ||
| id: 305, | ||
| url: 'https://picsum.photos/id/1080/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.009, | ||
| longitude: center.longitude + 0.001, | ||
| }, | ||
| }, | ||
| { | ||
| id: 306, | ||
| url: 'https://picsum.photos/id/1084/300/300', | ||
| location: { | ||
| latitude: center.latitude - 0.004, | ||
| longitude: center.longitude + 0.013, | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| const safeAlbums: AlbumWithPhotosResponse[] = photoListResponseMock.albums ?? []; | ||
|
|
||
| export const albumList: Album[] = safeAlbums.map((album) => { | ||
| const photoList = (album.photos ?? []) | ||
| .filter((photo) => Boolean(photo?.url)) | ||
| .map((photo) => ({ | ||
| photoId: String(photo.id ?? ''), | ||
| src: photo.url ?? '', | ||
| })); | ||
|
|
||
| return { | ||
| id: album.id ?? 0, | ||
| title: album.title ?? '알 수 없는 앨범', | ||
| photoList, | ||
| photoCount: album.photoCount ?? photoList.length, | ||
| }; | ||
| }); | ||
|
|
||
| export const albumDetailById = safeAlbums.reduce<Record<number, AlbumDetailData>>( | ||
| (acc, album) => { | ||
| const albumId = album.id ?? 0; | ||
| acc[albumId] = { | ||
| id: albumId, | ||
| title: album.title ?? '알 수 없는 앨범', | ||
| photos: (album.photos ?? []).map((photo) => ({ | ||
| id: photo.id ?? 0, | ||
| url: photo.url ?? '', | ||
| })), | ||
| }; | ||
| return acc; | ||
| }, | ||
| {}, | ||
| ); | ||
|
|
||
| export const mapPins: MapPin[] = safeAlbums.flatMap((album) => { | ||
| const albumId = album.id ?? 0; | ||
| return (album.photos ?? []) | ||
| .filter((photo) => { | ||
| const latitude = photo?.location?.latitude; | ||
| const longitude = photo?.location?.longitude; | ||
| return typeof latitude === 'number' && typeof longitude === 'number'; | ||
| }) | ||
| .map((photo) => ({ | ||
| id: photo.id ?? 0, | ||
| albumId, | ||
| latitude: photo.location?.latitude ?? center.latitude, | ||
| longitude: photo.location?.longitude ?? center.longitude, | ||
| imageUrl: photo.url ?? album.thumbnailUrl ?? 'https://picsum.photos/200/200', | ||
| imageCount: 1, | ||
| })); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import styled from '@emotion/styled'; | ||
|
|
||
| export const Wrapper = styled.div` | ||
| width: 100%; | ||
| height: 100vh; | ||
| position: relative; | ||
| `; | ||
|
|
||
| export const HeaderContainer = styled.div` | ||
| width: 100%; | ||
| position: absolute; | ||
| top: 0; | ||
| z-index: 10; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| 'use client'; | ||
| import { ExploreHeader, MenuHeader } from '@/components/header'; | ||
| import MapView from '@/components/map/MapView'; | ||
| import * as S from './page.styles'; | ||
| import { useEffect, useState } from 'react'; | ||
| import { getCurrentPosition } from '@/utils/getCurrentPosition'; | ||
| import { LocationState } from '@/types/map.type'; | ||
| import BottomSheet from '@/components/bottomSheet/BottomSheet'; | ||
| import { SheetContext } from '@/components/bottomSheet/_context/SheetContext'; | ||
| import { albumDetailById, albumList, mapPins } from './mockData'; | ||
|
|
||
| export default function MapPage() { | ||
| const [viewState, setViewState] = useState<LocationState | null>(null); | ||
| const [sheetContext, setSheetContext] = useState<SheetContext>({ | ||
| type: 'home', | ||
|
||
| }); | ||
|
|
||
| useEffect(() => { | ||
| const init = async () => { | ||
| try { | ||
| const pos = await getCurrentPosition(); | ||
|
|
||
| setViewState({ | ||
| latitude: pos.coords.latitude, | ||
| longitude: pos.coords.longitude, | ||
| zoom: 14, | ||
| }); | ||
| } catch (err) { | ||
| console.log(err); | ||
| setViewState({ | ||
| latitude: 37.5665, | ||
|
||
| longitude: 126.978, | ||
| zoom: 12, | ||
|
||
| }); | ||
| } | ||
| }; | ||
|
|
||
| init(); | ||
| }, []); | ||
|
|
||
| const selectedAlbumId = | ||
| sheetContext.type === 'albumDetail' ? sheetContext.albumId : null; | ||
| const selectedAlbumTitle = | ||
| selectedAlbumId !== null ? albumDetailById[selectedAlbumId]?.title : undefined; | ||
|
|
||
| const handleSelectAlbum = (albumId: number) => { | ||
| setSheetContext({ type: 'albumDetail', albumId }); | ||
| }; | ||
|
|
||
| const handleCloseAlbumDetail = () => { | ||
| setSheetContext({ type: 'albumList' }); | ||
| }; | ||
|
|
||
| return ( | ||
| <S.Wrapper> | ||
| <S.HeaderContainer> | ||
| {sheetContext.type === 'albumDetail' ? ( | ||
| <MenuHeader | ||
| title={selectedAlbumTitle ?? '앨범'} | ||
| onClickBack={handleCloseAlbumDetail} | ||
| /> | ||
| ) : ( | ||
| <ExploreHeader | ||
| title="서울특별시 마포구" | ||
| onClickProfile={() => {}} | ||
| onClickExplore={() => {}} | ||
| /> | ||
| )} | ||
| </S.HeaderContainer> | ||
| {viewState && ( | ||
| <MapView | ||
| locationState={viewState} | ||
| pins={mapPins} | ||
| selectedAlbumId={selectedAlbumId} | ||
| /> | ||
| )} | ||
| <BottomSheet | ||
| context={sheetContext} | ||
| albums={albumList} | ||
| albumDetailById={albumDetailById} | ||
| onChangeContext={setSheetContext} | ||
| onSelectAlbum={handleSelectAlbum} | ||
| /> | ||
| </S.Wrapper> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ID 기본값 0 사용 시 데이터 충돌 가능성
album.id ?? 0을 사용하면 여러 앨범의 ID가 undefined일 경우 모두 키 0으로 저장되어 데이터가 덮어씌워질 수 있습니다. 현재는 목 데이터라 문제가 없지만, 실제 API 연동 시 이 패턴이 그대로 사용되면 버그로 이어질 수 있습니다.💡 고유 ID 생성 또는 필터링 제안
📝 Committable suggestion
🤖 Prompt for AI Agents