diff --git a/package-lock.json b/package-lock.json index 58200cc..a437878 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,14 @@ "@tanstack/react-query-devtools": "^5.36.0", "axios": "^1.6.8", "framer-motion": "^11.1.9", + "haversine": "^1.1.1", "next": "14.2.3", "react": "^18", "react-cookie": "^7.1.4", "react-dom": "^18", "react-hook-form": "^7.51.4", "react-icons": "^5.2.1", + "react-id-swiper": "^4.0.0", "react-markdown": "^9.0.1", "react-query": "^3.39.3", "react-toastify": "^10.0.5", @@ -26,6 +28,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.13", + "@types/navermaps": "^3.7.5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -725,6 +728,12 @@ "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -770,6 +779,15 @@ "@types/node": "*" } }, + "node_modules/@types/navermaps": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@types/navermaps/-/navermaps-3.7.5.tgz", + "integrity": "sha512-RzqYi0K5xhs45+KLkeoWjz2niDjVwKMYS1/LKns2LH9L7LEMVabdmyhP7n/6fzdJnbHc1wD1Dz+lFKOgq4yzJQ==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "20.12.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", @@ -2982,6 +3000,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/haversine": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/haversine/-/haversine-1.1.1.tgz", + "integrity": "sha512-KW4MS8+krLIeiw8bF5z532CptG0ZyGGFj0UbKMxx25lKnnJ1hMUbuzQl+PXQjNiDLnl1bOyz23U6hSK10r4guw==" + }, "node_modules/headers-polyfill": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", @@ -4787,7 +4810,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5410,6 +5432,22 @@ "react": "*" } }, + "node_modules/react-id-swiper": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/react-id-swiper/-/react-id-swiper-4.0.0.tgz", + "integrity": "sha512-BFY8VQYgc1ECkT1Ek6MYSF8MADjWYZctdeRlMA202mjGcdjq1Bugfhg207KHTjGWKWZlY/7/nxFShqQo74RslA==", + "dependencies": { + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">= 6.11.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0", + "swiper": ">=5.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6178,6 +6216,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swiper": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.3.tgz", + "integrity": "sha512-80MSxonyTxrGcaWj9YgvvhD8OG0B9/9IVZP33vhIEvyWvmKjnQDBieO+29wKvMx285sAtvZyrWBdkxaw6+D3aw==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/swiperjs" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "peer": true, + "engines": { + "node": ">= 4.7.0" + } + }, "node_modules/synckit": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", diff --git a/package.json b/package.json index bd1043d..3a3e383 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,14 @@ "@tanstack/react-query-devtools": "^5.36.0", "axios": "^1.6.8", "framer-motion": "^11.1.9", + "haversine": "^1.1.1", "next": "14.2.3", "react": "^18", "react-cookie": "^7.1.4", "react-dom": "^18", "react-hook-form": "^7.51.4", "react-icons": "^5.2.1", + "react-id-swiper": "^4.0.0", "react-markdown": "^9.0.1", "react-query": "^3.39.3", "react-toastify": "^10.0.5", @@ -27,6 +29,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.13", + "@types/navermaps": "^3.7.5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/public/map/CoffeeImg.svg b/public/map/CoffeeImg.svg new file mode 100644 index 0000000..935006d --- /dev/null +++ b/public/map/CoffeeImg.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/map/LoungeImg.svg b/public/map/LoungeImg.svg new file mode 100644 index 0000000..32dca3c --- /dev/null +++ b/public/map/LoungeImg.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/map/MapLocation.png b/public/map/MapLocation.png new file mode 100644 index 0000000..f4f7005 Binary files /dev/null and b/public/map/MapLocation.png differ diff --git a/public/map/MapLocationActive.png b/public/map/MapLocationActive.png new file mode 100644 index 0000000..7a0e5c1 Binary files /dev/null and b/public/map/MapLocationActive.png differ diff --git a/public/map/MyLocation.png b/public/map/MyLocation.png new file mode 100644 index 0000000..746aecb Binary files /dev/null and b/public/map/MyLocation.png differ diff --git a/public/map/OfficeActive.svg b/public/map/OfficeActive.svg new file mode 100644 index 0000000..d6b99cf --- /dev/null +++ b/public/map/OfficeActive.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/map/OfficeCallImg.svg b/public/map/OfficeCallImg.svg new file mode 100644 index 0000000..9ec5857 --- /dev/null +++ b/public/map/OfficeCallImg.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/map/OfficeDefaultImg.png b/public/map/OfficeDefaultImg.png new file mode 100644 index 0000000..7d1584e Binary files /dev/null and b/public/map/OfficeDefaultImg.png differ diff --git a/public/map/OfficeDefaultImg2.png b/public/map/OfficeDefaultImg2.png new file mode 100644 index 0000000..f9d02ae Binary files /dev/null and b/public/map/OfficeDefaultImg2.png differ diff --git a/public/map/OfficeInActive.svg b/public/map/OfficeInActive.svg new file mode 100644 index 0000000..7df7270 --- /dev/null +++ b/public/map/OfficeInActive.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/map/OfficeInfo.svg b/public/map/OfficeInfo.svg new file mode 100644 index 0000000..6985067 --- /dev/null +++ b/public/map/OfficeInfo.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/map/OfficeLocationSmall.svg b/public/map/OfficeLocationSmall.svg new file mode 100644 index 0000000..d4996ac --- /dev/null +++ b/public/map/OfficeLocationSmall.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/map/OfficeLocationSmall1.svg b/public/map/OfficeLocationSmall1.svg new file mode 100644 index 0000000..7e34a1a --- /dev/null +++ b/public/map/OfficeLocationSmall1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/map/OfficeParkImg.svg b/public/map/OfficeParkImg.svg new file mode 100644 index 0000000..c7a5828 --- /dev/null +++ b/public/map/OfficeParkImg.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/map/ParcelImg.svg b/public/map/ParcelImg.svg new file mode 100644 index 0000000..33efcb5 --- /dev/null +++ b/public/map/ParcelImg.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/map/PhoneBooseImg.svg b/public/map/PhoneBooseImg.svg new file mode 100644 index 0000000..62f9756 --- /dev/null +++ b/public/map/PhoneBooseImg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/map/PrinterImg.svg b/public/map/PrinterImg.svg new file mode 100644 index 0000000..795e763 --- /dev/null +++ b/public/map/PrinterImg.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/map/RechargeImg.svg b/public/map/RechargeImg.svg new file mode 100644 index 0000000..deec6a0 --- /dev/null +++ b/public/map/RechargeImg.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/map/Search.png b/public/map/Search.png new file mode 100644 index 0000000..4113435 Binary files /dev/null and b/public/map/Search.png differ diff --git a/public/map/SnackBarImg.svg b/public/map/SnackBarImg.svg new file mode 100644 index 0000000..f667451 --- /dev/null +++ b/public/map/SnackBarImg.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/map/SuppliesImg.svg b/public/map/SuppliesImg.svg new file mode 100644 index 0000000..0506e58 --- /dev/null +++ b/public/map/SuppliesImg.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/map/triangle.svg b/public/map/triangle.svg new file mode 100644 index 0000000..ef2dfee --- /dev/null +++ b/public/map/triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/api/map/getOffice.ts b/src/api/map/getOffice.ts new file mode 100644 index 0000000..6240261 --- /dev/null +++ b/src/api/map/getOffice.ts @@ -0,0 +1,24 @@ +const backendUrl = process.env.NEXT_PUBLIC_BASE_URL; + +export const getBranchInfo = async () => { + try { + const token = document.cookie.replace(/(?:(?:^|.*;\s*)token\s*=\s*([^;]*).*$)|^.*$/, "$1"); + const response = await fetch(`${backendUrl}/branches`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + cache: 'no-store' + }); + if (!response.ok) { + throw new Error('Failed to fetch branch information'); + } + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching branch info:', error); + throw error; + } +}; + diff --git a/src/api/types/branch.ts b/src/api/types/branch.ts new file mode 100644 index 0000000..c5895ab --- /dev/null +++ b/src/api/types/branch.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-unused-vars */ +export interface Branch { + branchName: string; + branchAddress: string; + branchLatitude: number; + branchLongitude: number; +} + +export interface ModalProps { + isOpen: boolean; + onClose: () => void; + branchName: string; + branchAddress: string; +} + +export interface MapSearchBarProps { + onFocus: () => void; + onChange: (value: string) => void +} + +export interface MapSearchResultProps { + onClose: () => void; + results: Branch[]; + onMarkerClick: (branch: Branch) => void; + currentLatitude: number; + currentLongitude: number; +} + +export interface OfficeInfoProps { + branchName: string; + branchAddress: string; +} \ No newline at end of file diff --git a/src/components/map/MapSearchBar.tsx b/src/components/map/MapSearchBar.tsx new file mode 100644 index 0000000..6e39516 --- /dev/null +++ b/src/components/map/MapSearchBar.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +interface MapSearchBarProps { + onFocus: () => void; + // eslint-disable-next-line no-unused-vars + onChange: (value: string) => void +} + +const MapSearchBar: React.FC = ({ onFocus, onChange }) => { + const handleInputChange = (event: React.ChangeEvent) => { + onChange(event.target.value); + }; + + return ( +
+
+ + Search Icon +
+
+ ); +}; + +export default MapSearchBar; diff --git a/src/components/map/MapSearchResult.tsx b/src/components/map/MapSearchResult.tsx new file mode 100644 index 0000000..6cab580 --- /dev/null +++ b/src/components/map/MapSearchResult.tsx @@ -0,0 +1,63 @@ +import React, { useState } from 'react'; +import { Branch } from '@/api/types/branch'; +import Image from 'next/image'; +import { calculateDistance, formatDistance } from '@/utils/calculateDistance'; +import { MapSearchResultProps } from '@/api/types/branch'; + +const MapSearchResult: React.FC = ({ onClose, results, onMarkerClick, currentLatitude, currentLongitude }) => { + const [searchTerm, setSearchTerm] = useState(''); + const filteredResults = results + .filter(branch => branch.branchName.includes(searchTerm)) + .sort((a, b) => { + const distanceA = calculateDistance(currentLatitude, currentLongitude, a.branchLatitude, a.branchLongitude); + const distanceB = calculateDistance(currentLatitude, currentLongitude, b.branchLatitude, b.branchLongitude); + return distanceA - distanceB; + }); + + const handleItemClick = (branch: Branch) => { + onMarkerClick(branch); + onClose(); + }; + + return ( +
+
+ setSearchTerm(e.target.value)} + /> + Search Icon + +
+ {searchTerm && ( +
+ {filteredResults.length === 0 ? ( +

검색 결과가 없습니다.

+ ) : ( +
    + {filteredResults.map(branch => ( +
  • handleItemClick(branch)}> + Location + {branch.branchName} + {formatDistance(calculateDistance(currentLatitude, currentLongitude, branch.branchLatitude, branch.branchLongitude))} +
  • + ))} +
+ )} +
+ )} +
+ ); +}; + +export default MapSearchResult; diff --git a/src/components/map/OfficeInfo.tsx b/src/components/map/OfficeInfo.tsx new file mode 100644 index 0000000..e8dce25 --- /dev/null +++ b/src/components/map/OfficeInfo.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import Image from 'next/image'; +import { useRouter } from 'next/router'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import 'swiper/css'; +import 'swiper/css/pagination'; +import { Pagination } from 'swiper/modules'; +import { IoIosArrowRoundBack } from 'react-icons/io'; +import { OfficeInfoProps } from '@/api/types/branch'; + +const OfficeInfo: React.FC = ({ branchName, branchAddress }) => { + const router = useRouter(); + + const handleBackClick = () => { + router.push('/map'); + }; + + return ( +
+
+ + {branchName} +
+
+
+ + + Office Image 1 + + + Office Image 2 + + + Office Image 3 + + +
+
+
+
찾아오는길
+
+ OfficeLocationSmall +

{branchAddress}

+
+
+ OfficeCallImg +

{branchAddress}

+
+
+ OfficeParkImg +

{branchAddress}

+
+
+
+
+
공용 공간 리스트
+
    +
  • A룸 - 10명 수용 가능
  • +
  • B룸 - 9명 수용 가능
  • +
  • C룸 - 13명 수용 가능
  • +
+
+
+
+
편의시설
+
+
+ LoungeImg +

라운지

+
+
+ RechargeImg +

리차징룸

+
+
+ ParcelImg +

무인택배

+
+
+ PhoneBooseImg +

폰부스

+
+
+ PrinterImg +

복합기

+
+
+ SnackBarImg +

스낵바

+
+
+ SuppliesImg +

사무용품

+
+
+ CoffeeImg +

무제한 커피

+
+
+
+
+
+
공지사항
+
+
+ +
+
+
+ ); +}; + +export default OfficeInfo; diff --git a/src/components/map/OfficeModal.tsx b/src/components/map/OfficeModal.tsx new file mode 100644 index 0000000..34fa792 --- /dev/null +++ b/src/components/map/OfficeModal.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useRef } from 'react'; +import Image from 'next/image'; +import { ModalProps } from '@/api/types/branch'; +import { useRouter } from 'next/router'; + +const OfficeModal: React.FC = ({ isOpen, onClose, branchName, branchAddress }) => { + const modalRef = useRef(null); + const router = useRouter(); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (modalRef.current && !modalRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + const handleOfficeInfo = () => { + router.push({ + pathname: `/branches/${encodeURIComponent(branchName)}`, + query: { address: branchAddress }, + }); + }; + + return ( + + ); +}; + +export default OfficeModal; diff --git a/src/components/map/UseMap.tsx b/src/components/map/UseMap.tsx new file mode 100644 index 0000000..ef89f4b --- /dev/null +++ b/src/components/map/UseMap.tsx @@ -0,0 +1,218 @@ +import React, { useEffect, useRef, useState } from 'react'; +import Image from 'next/image'; +import MapSearchBar from './MapSearchBar'; +import MapSearchResult from './MapSearchResult'; +import { getBranchInfo } from '@/api/map/getOffice'; +import { Branch } from '@/api/types/branch'; +import OfficeModal from './OfficeModal'; + +const UseMap: React.FC = () => { + const mapRef = useRef(null); + const markerRef = useRef(null); + const markerRefs = useRef<{ [key: string]: naver.maps.Marker }>({}); + const [imageSrc, setImageSrc] = useState('/map/MapLocation.png'); + const [showMessage, setShowMessage] = useState(true); + const [showSearchResults, setShowSearchResults] = useState(false); + const [loading, setLoading] = useState(false); + const [branches, setBranches] = useState([]); + const [map, setMap] = useState(null); + const [selectedBranch, setSelectedBranch] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [filteredBranches, setFilteredBranches] = useState([]); + const [currentLatitude, setCurrentLatitude] = useState(37.4979); + const [currentLongitude, setCurrentLongitude] = useState(127.0276); + const [selectedMarker, setSelectedMarker] = useState(null); + + useEffect(() => { + const initMap = () => { + if (mapRef.current) { + const initialCenter = new naver.maps.LatLng(37.4979, 127.0276); + const mapOptions: naver.maps.MapOptions = { + center: initialCenter, + zoom: 16, + }; + const mapInstance = new naver.maps.Map(mapRef.current, mapOptions); + setMap(mapInstance); + } + }; + + if (typeof window !== 'undefined' && window.naver) { + initMap(); + } else { + window.addEventListener('load', initMap); + return () => window.removeEventListener('load', initMap); + } + }, []); + + useEffect(() => { + getBranchInfo() + .then((response) => { + console.log('Branch Info:', response); + setBranches(response.data); + }) + .catch((error) => { + console.error('Error fetching branch info:', error); + }); + }, []); + + useEffect(() => { + if (map && branches.length > 0) { + setMarkers(map); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [branches, map]); + + useEffect(() => { + if (map) { + setMarkers(map); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedMarker]); + + useEffect(() => { + const filtered = branches.filter(branch => branch.branchName.includes(searchQuery)); + setFilteredBranches(filtered); + }, [searchQuery, branches]); + + const setMarkers = (map: naver.maps.Map) => { + Object.values(markerRefs.current).forEach(marker => marker.setMap(null)); + markerRefs.current = {}; + + branches.forEach((branch) => { + const isSelected = selectedMarker === branch.branchName; + const marker = new naver.maps.Marker({ + position: new naver.maps.LatLng(branch.branchLatitude, branch.branchLongitude), + map: map, + icon: { + url: '/map/OfficeActive.svg', + size: new naver.maps.Size(48, 48), + scaledSize: new naver.maps.Size(isSelected ? 60 : 48, isSelected ? 60 : 48), + origin: new naver.maps.Point(0, 0), + anchor: new naver.maps.Point(24, 24), + }, + }); + naver.maps.Event.addListener(marker, 'click', () => { + handleMarkerClick(branch); + }); + markerRefs.current[branch.branchName] = marker; + }); + }; + + useEffect(() => { + const handleCurrentLocation = () => { + setLoading(true); + if (navigator.geolocation && map) { + navigator.geolocation.getCurrentPosition( + (position) => { + const { latitude, longitude } = position.coords; + setCurrentLatitude(latitude); + setCurrentLongitude(longitude); + + const currentLocation = new naver.maps.LatLng(latitude, longitude); + map.panTo(currentLocation); + if (markerRef.current) { + markerRef.current.setPosition(currentLocation); + } else { + markerRef.current = new naver.maps.Marker({ + position: currentLocation, + map: map, + icon: { + url: '/map/MyLocation.png', + size: new naver.maps.Size(48, 48), + scaledSize: new naver.maps.Size(48, 48), + origin: new naver.maps.Point(0, 0), + anchor: new naver.maps.Point(24, 24), + }, + }); + } + setLoading(false); + }, + (error) => { + console.error('Error getting current location:', error); + setLoading(false); + } + ); + } else { + alert('Geolocation is not supported by this browser.'); + setLoading(false); + } + }; + + const button = document.getElementById('current-location-button'); + if (button) { + button.addEventListener('click', handleCurrentLocation); + } + + return () => { + if (button) { + button.removeEventListener('click', handleCurrentLocation); + } + }; + }, [map]); + + const handleDismissMessage = () => { + setShowMessage(false); + }; + + const handleMarkerClick = (branch: Branch) => { + const position = new naver.maps.LatLng(branch.branchLatitude, branch.branchLongitude); + map?.panTo(position); + + setSelectedBranch(branch); + setIsModalOpen(true); + setSelectedMarker(branch.branchName); + }; + + const handleSearchQueryChange = (query: string) => { + setSearchQuery(query); + }; + + return ( +
+
+ {showMessage && ( + <> +
+ 더 정확한 접속위치를 확인해보세요! + +
+ Current Location + + )} + setShowSearchResults(true)} onChange={handleSearchQueryChange} /> + {showSearchResults && ( + setShowSearchResults(false)} + results={filteredBranches} + onMarkerClick={handleMarkerClick} + currentLatitude={currentLatitude} + currentLongitude={currentLongitude} + /> + )} + setIsModalOpen(false)} + branchName={selectedBranch?.branchName || ''} + branchAddress={selectedBranch?.branchAddress || ''} + /> + + + {loading && ( +
+
+
+ )} +
+ ); +}; + +export default UseMap; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 607e7f7..72b761f 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -2,14 +2,20 @@ import MockProvider from '@/providers/MockProvider'; import QueryProvider from '@/providers/QueryProvider'; import '@/styles/globals.css'; import type { AppProps } from 'next/app'; +import Script from 'next/script'; import { QueryClient, QueryClientProvider } from 'react-query'; + const queryClient = new QueryClient(); export default function App({ Component, pageProps }: AppProps) { return ( + diff --git a/src/pages/branches/[name].tsx b/src/pages/branches/[name].tsx new file mode 100644 index 0000000..af04ac4 --- /dev/null +++ b/src/pages/branches/[name].tsx @@ -0,0 +1,20 @@ +import { useRouter } from 'next/router'; +import MainContainer from '@/components/shared/MainContainer'; +import OfficeInfo from '@/components/map/OfficeInfo'; + +const BranchDetailsPage = () => { + const router = useRouter(); + const { name, address } = router.query; + + return ( + +
+
+ +
+
+
+ ); +}; + +export default BranchDetailsPage; diff --git a/src/pages/map/index.tsx b/src/pages/map/index.tsx new file mode 100644 index 0000000..3e428c7 --- /dev/null +++ b/src/pages/map/index.tsx @@ -0,0 +1,16 @@ +import MainContainer from '@/components/shared/MainContainer'; +import UseMap from '@/components/map/UseMap'; + +const MapPage = () => { + return ( + +
+
+ +
+
+
+ ); +}; + +export default MapPage; diff --git a/src/styles/globals.css b/src/styles/globals.css index 4350919..c61751d 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -42,3 +42,17 @@ body { text-wrap: balance; } } + +.loader { + border: 4px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top: 4px solid #fff; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} \ No newline at end of file diff --git a/src/types/naver-maps.d.ts b/src/types/naver-maps.d.ts new file mode 100644 index 0000000..ba5456b --- /dev/null +++ b/src/types/naver-maps.d.ts @@ -0,0 +1,30 @@ +/* eslint-disable no-unused-vars */ +declare namespace naver.maps { + class Map { + panTo: any; + constructor(element: HTMLElement | string, options: MapOptions); + setCenter(latlng: LatLng): void; + setZoom(zoom: number): void; + } + + class LatLng { + constructor(lat: number, lng: number); + } + + class Marker { + constructor(options: MarkerOptions); + setMap(map: Map | null): void; + setPosition(latlng: LatLng): void; + } + + interface MapOptions { + center: LatLng; + zoom: number; + } + + interface MarkerOptions { + position: LatLng; + map: Map; + } + } + \ No newline at end of file diff --git a/src/utils/calculateDistance.ts b/src/utils/calculateDistance.ts new file mode 100644 index 0000000..f571537 --- /dev/null +++ b/src/utils/calculateDistance.ts @@ -0,0 +1,24 @@ +export const calculateDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => { + const R = 6371e3; + const φ1 = lat1 * Math.PI / 180; + const φ2 = lat2 * Math.PI / 180; + const Δφ = (lat2-lat1) * Math.PI / 180; + const Δλ = (lon2-lon1) * Math.PI / 180; + + const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + + Math.cos(φ1) * Math.cos(φ2) * + Math.sin(Δλ/2) * Math.sin(Δλ/2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + const d = R * c; + + return d; +}; + +export const formatDistance = (distance: number): string => { + if (distance < 1000) { + return `${Math.round(distance)}m`; + } else { + return `${(distance / 1000).toFixed(2)}km`; + } +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8464797..32c2307 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,6 @@ "@store/*": ["src/store/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.mjs"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.mjs", "types"], "exclude": ["node_modules"] }