Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/front-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ jobs:
- name: install npm dependencies
run: npm install

- name: Create .env file
run: |
echo "VITE_NAVER_MAP_ID=${{ secrets.VITE_NAVER_MAP_ID }}" >> .env
echo "VITE_SERVER_ADDRESS=${{ secrets.VITE_SERVER_ADDRESS }}" >> .env

- name: react build
run: npm run build

Expand Down
Binary file added src/assets/a.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/b.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/c.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/d.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/f.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
80 changes: 65 additions & 15 deletions src/pages/MapPage/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { BottomSheetProps } from './interface';
import ListContainer from './components/ListContainer';
import CommentList from './components/comment/CommentList';
import NoInfo from './components/NoInfo';
import SearchResult from './components/SearchResult';
import { Slope } from '../../apis/slopeMap';

const BottomSheet: React.FC<BottomSheetProps> = ({
slopeData,
Expand Down Expand Up @@ -75,6 +77,40 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
scrollWrapperRef.current.style.overflow = 'auto';
}
};

const countGrades = (slopes: Slope[]) => {
const counts = {
aCount: 0,
bCount: 0,
cCount: 0,
dCount: 0,
fCount: 0,
};

for (let i = 0; i < slopes.length; i++) {
const grade = slopes[i].priority.grade.toUpperCase();

switch (grade) {
case 'A':
counts.aCount++;
break;
case 'B':
counts.bCount++;
break;
case 'C':
counts.cCount++;
break;
case 'D':
counts.dCount++;
break;
case 'F':
counts.fCount++;
break;
}
}
return counts;
};

return (
<BaseContainer height={height} $isDragging={isDragging.current}>
<ScrollWrapper ref={scrollWrapperRef}>
Expand Down Expand Up @@ -103,22 +139,36 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
return <NoInfo text="5km 반경 내 급경사지 데이터가 없습니다." />;
}
} else {
const { aCount, bCount, cCount, dCount, fCount } =
countGrades(slopeData);
return (
<ListWrapper>
{!slopeData || slopeData.length === 0 ? (
<div>데이터 조회 중</div>
) : (
slopeData.map((item, index) => (
<ListContainer
key={index}
item={item}
onClick={() => {
onItemClick(item, index);
}}
></ListContainer>
))
)}
</ListWrapper>
<>
<SearchResult
resultCount={slopeData.length}
gradeCount={{
A: aCount,
B: bCount,
C: cCount,
D: dCount,
F: fCount,
}}
/>
<ListWrapper>
{!slopeData || slopeData.length === 0 ? (
<div>데이터 조회 중</div>
) : (
slopeData.map((item, index) => (
<ListContainer
key={index}
item={item}
onClick={() => {
onItemClick(item, index);
}}
></ListContainer>
))
)}
</ListWrapper>
</>
);
}
})()}
Expand Down
231 changes: 57 additions & 174 deletions src/pages/MapPage/MapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,158 +1,85 @@
import { useState, useEffect, useCallback } from 'react';
import { useEffect } from 'react';

import styled from 'styled-components';

import BottomSheet from './BottomSheet';
import MapComponent from './components/map/MapComponent';
import SearchComponent from './components/map/Search';

import { Slope, slopeMapAPI } from '../../apis/slopeMap';
import MyLocationIcon from '@mui/icons-material/MyLocationRounded';
import { useMapStore } from './mapStore';
import ButtonGroup from './components/ButtonGroup';
const MapPage = () => {
// console.log(escarpmentData);
// console.log(escarpmentData);
const [selectedMarkerId, setSelectedMarkerId] = useState<number | null>(null);
const [allTextShow, setAllTextShow] = useState<boolean>(false);
const [userLocation, setUserLocation] = useState<naver.maps.LatLng | null>(
null
);
const [slopeData, setSlopeData] = useState<Slope[]>([]);

const [searchMod, setSearchMod] = useState<boolean>(false);
const [bottomSheetHeight, setBottomSheetHeight] = useState<number>(200); //bottomsheet 높이 조절 state

const fetchSlopes = useCallback(async () => {
//위치정보가 없는 경우 호출 안함
if (!userLocation?.lat() || !userLocation?.lng()) return;

try {
const data = await slopeMapAPI.fetchNearbySlopes(
userLocation.lat(),
userLocation.lng()
);
setSlopeData(data || []);
} catch (error) {
console.error('Error fetching slopes:', error);
setSlopeData([]);
}
}, [userLocation]);
const {
selectedMarkerId,
allTextShow,
userLocation,
slopeData,
searchMod,
bottomSheetHeight,
mapInstance,
setBottomSheetHeight,
fetchSlopes,
handleSearch,
chooseSelectItem,
setUserLocation,
setMapInstance,
setSelectedMarkerId,
} = useMapStore();

useEffect(() => {
if (!searchMod) fetchSlopes();
}, [userLocation, searchMod, fetchSlopes]);
const [mapInstance, setMapInstance] = useState<naver.maps.Map | null>(null);

//검색 핸들 callback
const handleSearch = useCallback(
(searchValue: string) => {
if (searchValue === '') {
setSearchMod(false);
fetchSlopes();
setSelectedMarkerId(null);
return;
}
setSelectedMarkerId(null);
setSearchMod(true);

const searchSlope = async () => {
if (!userLocation?.lat() || !userLocation?.lng()) return; //위치정보가 없는 경우 호출 안함
// console.log('Searching for:', searchValue);
// console.log('Searching Mod:', searchMod);
try {
const data = await slopeMapAPI.searchSlopes(
searchValue,
userLocation.lat(),
userLocation.lng()
);
setSlopeData(data || []);
if (mapInstance && data) {
const coordinates = data[0].location.coordinates.start.coordinates;
mapInstance.panTo(
new naver.maps.LatLng(coordinates[1], coordinates[0])
);
}
} catch (error) {
console.error('Error search slopes:', error);
setSlopeData([]);
}
};
searchSlope();
},
[userLocation]
);

//아이템 선택
const chooseSelectItem = useCallback(
(item: Slope, index: number) => {
if (mapInstance && item) {
// 지도 이동
const coordinates = item.location.coordinates.start.coordinates;
mapInstance.panTo(
new naver.maps.LatLng(coordinates[1], coordinates[0])
);

// 마커 선택 상태 변경
setSelectedMarkerId((prevId) => (prevId === index ? null : index));
}
},
[mapInstance]
);

//내 위치로 이동
const moveToMyLocation = useCallback(() => {
if (!mapInstance || !userLocation) return;

// 줌 레벨 먼저 변경
mapInstance.setZoom(15);

// 약간 지연 후 위치 이동
setTimeout(() => {
mapInstance.panTo(userLocation);
}, 100);

fetchSlopes();
}, [mapInstance, userLocation, fetchSlopes]);
return (
<BaseBackground>
<MapComponent
selectedMarkerId={selectedMarkerId}
escarpmentData={slopeData}
allTextShow={allTextShow}
userLocation={userLocation}
setUserLocation={setUserLocation}
mapInstance={mapInstance}
setMapInstance={setMapInstance}
onMarkerClick={chooseSelectItem}
/>
<BottomSheet
slopeData={slopeData}
selectItem={
selectedMarkerId !== null ? slopeData[selectedMarkerId] : null
}
onItemClick={chooseSelectItem}
height={bottomSheetHeight}
setHeight={setBottomSheetHeight}
onCloseInfo={() => {
setSelectedMarkerId(null);
<>
<BaseBackground>
<MapComponent
selectedMarkerId={selectedMarkerId}
escarpmentData={slopeData}
allTextShow={allTextShow}
userLocation={userLocation}
setUserLocation={setUserLocation}
mapInstance={mapInstance}
setMapInstance={setMapInstance}
onMarkerClick={chooseSelectItem}
/>
<BottomSheet
slopeData={slopeData}
selectItem={
selectedMarkerId !== null ? slopeData[selectedMarkerId] : null
}
onItemClick={chooseSelectItem}
height={bottomSheetHeight}
setHeight={setBottomSheetHeight}
onCloseInfo={() => {
setSelectedMarkerId(null);
}}
searchMod={searchMod}
/>

<SearchComponent onSearch={handleSearch} />
{/* <AllShowButton
$isSelect={allTextShow}
onClick={() => {
setAllTextShow(!allTextShow);
}}
searchMod={searchMod}
/>

<SearchComponent onSearch={handleSearch} />
>
{allTextShow ? '위성지도' : '일반지도'}
</AllShowButton>
<AllShowButton
$isSelect={allTextShow}
onClick={() => {
// console.log(allTextShow);
// console.log(allTextShow);
setAllTextShow(!allTextShow);
}}
>
{allTextShow ? '전체표기' : '개별표기'}
</AllShowButton>
<MyPosition onClick={moveToMyLocation}>
<MyLocationIcon />
</MyPosition>
</BaseBackground>
</MyPosition> */}
<ButtonGroup />
</BaseBackground>
</>
);
};

Expand All @@ -166,47 +93,3 @@ const BaseBackground = styled.div`
overscroll-behavior: none;
position: relative;
`;

const AllShowButton = styled.button<{ $isSelect: boolean }>`
position: absolute;
top: 50px;
right: 10px;
border: none;
border-radius: 8px;
height: 30px;
padding: 5px 10px;
box-shadow: ${({ theme }) => theme.shadows.sm};
font-weight: ${({ theme }) => theme.fonts.weights.bold};
font-size: ${({ theme }) => theme.fonts.sizes.ms};
background-color: ${({ $isSelect, theme }) =>
$isSelect ? theme.colors.primaryDark : '#fff'};
color: ${({ $isSelect, theme }) =>
!$isSelect ? theme.colors.primaryDark : '#fff'};
&:focus {
outline: none;
}
transition: all 0.15s ease-in-out;

&:active {
transform: scale(1.1);
}
`;

const MyPosition = styled.button`
position: absolute;
top: 90px;
right: 10px;
border: none;
border-radius: 8px;
padding: 5px 10px;
box-shadow: ${({ theme }) => theme.shadows.sm};
font-weight: 550;
background-color: #fff;
transition: all 0.15s ease-in-out;
&:hover {
background-color: ${({ theme }) => theme.colors.grey[200]};
}
&:active {
transform: scale(1.1);
}
`;
Loading
Loading