diff --git a/index.html b/index.html
index e68e052..bbb0e16 100644
--- a/index.html
+++ b/index.html
@@ -9,6 +9,10 @@
+
Gieoghaebom
diff --git a/package-lock.json b/package-lock.json
index c7168f7..121832b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
+ "react-kakao-maps-sdk": "^1.1.27",
"react-lottie": "^1.2.4",
"react-router-dom": "^6.22.3",
"react-slick": "^0.30.2",
@@ -6215,6 +6216,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/kakao.maps.d.ts": {
+ "version": "0.1.40",
+ "resolved": "https://registry.npmjs.org/kakao.maps.d.ts/-/kakao.maps.d.ts-0.1.40.tgz",
+ "integrity": "sha512-nX69MB1ok04epe3OqS+/tEeWBbU31GSQbvDPJmQRRltzzqn6t4jBsO5v1nzalUjCKzwcH2CptOc767NZ7Hbu3g==",
+ "license": "MIT"
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -7211,6 +7218,20 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-kakao-maps-sdk": {
+ "version": "1.1.27",
+ "resolved": "https://registry.npmjs.org/react-kakao-maps-sdk/-/react-kakao-maps-sdk-1.1.27.tgz",
+ "integrity": "sha512-1EwYkYsjTDRFqysKStDasFMrFTXcLx2AyRlqMoWD7ONWhRqpjx9M874hkhEEHrnypP2eSIhhDLe0EiSKp3bd2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.22.15",
+ "kakao.maps.d.ts": "^0.1.39"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18",
+ "react-dom": "^16.8 || ^17 || ^18"
+ }
+ },
"node_modules/react-lottie": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/react-lottie/-/react-lottie-1.2.4.tgz",
diff --git a/package.json b/package.json
index 148e97c..89b5fbf 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
+ "react-kakao-maps-sdk": "^1.1.27",
"react-lottie": "^1.2.4",
"react-router-dom": "^6.22.3",
"react-slick": "^0.30.2",
diff --git a/src/apis/instance.ts b/src/apis/instance.ts
index 80b7852..5b1ac27 100644
--- a/src/apis/instance.ts
+++ b/src/apis/instance.ts
@@ -2,10 +2,15 @@ import axios, { AxiosInstance } from "axios";
export const imgInstance: AxiosInstance = axios.create({
baseURL: `https://picsum.photos/v2`,
- timeout: 5000
+ timeout: 1000
});
export const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 50000
});
+
+export const flaskInstance = axios.create({
+ baseURL: import.meta.env.VITE_API_FLASK_BASE_URL,
+ timeout: 50000
+});
diff --git a/src/apis/postAroundSeniorCenter.ts b/src/apis/postAroundSeniorCenter.ts
new file mode 100644
index 0000000..762041b
--- /dev/null
+++ b/src/apis/postAroundSeniorCenter.ts
@@ -0,0 +1,29 @@
+import { MapProps } from "@components/KakaoMap/KakaoMap.types";
+import { flaskInstance } from "./instance";
+
+const postAroundSeniorCenter = async (latitude: number, longitude: number): Promise => {
+ const postAroundSeniorCenterURL = "/locations";
+
+ try {
+ const accessToken = sessionStorage.getItem("accessToken");
+
+ const requestBody = {
+ latitude,
+ longitude
+ };
+
+ const config = {
+ headers: {
+ Authorization: `Bearer ${accessToken}`
+ }
+ };
+
+ const response = await flaskInstance.post(postAroundSeniorCenterURL, requestBody, config);
+
+ return response.data;
+ } catch (error) {
+ throw error;
+ }
+};
+
+export default postAroundSeniorCenter;
diff --git a/src/apis/postSeniorCenterList.ts b/src/apis/postSeniorCenterList.ts
new file mode 100644
index 0000000..bb3a281
--- /dev/null
+++ b/src/apis/postSeniorCenterList.ts
@@ -0,0 +1,29 @@
+import { MapProps } from "@components/KakaoMap/KakaoMap.types";
+import { flaskInstance } from "./instance";
+
+const postSeniorCenterList = async (page: number, keyword: string): Promise => {
+ const postSeniorCenterListURL = "/search";
+
+ try {
+ const accessToken = sessionStorage.getItem("accessToken");
+
+ const requestBody = {
+ keyword,
+ page
+ };
+
+ const config = {
+ headers: {
+ Authorization: `Bearer ${accessToken}`
+ }
+ };
+
+ const response = await flaskInstance.post(postSeniorCenterListURL, requestBody, config);
+
+ return response.data;
+ } catch (error) {
+ throw error;
+ }
+};
+
+export default postSeniorCenterList;
diff --git a/src/assets/icons/backArrow.svg b/src/assets/icons/backArrow.svg
new file mode 100644
index 0000000..9bdea57
--- /dev/null
+++ b/src/assets/icons/backArrow.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/listIcon.svg b/src/assets/icons/listIcon.svg
new file mode 100644
index 0000000..70e700d
--- /dev/null
+++ b/src/assets/icons/listIcon.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/mapIcon.svg b/src/assets/icons/mapIcon.svg
new file mode 100644
index 0000000..997b219
--- /dev/null
+++ b/src/assets/icons/mapIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/phoneIcon.svg b/src/assets/icons/phoneIcon.svg
new file mode 100644
index 0000000..0b3f1fa
--- /dev/null
+++ b/src/assets/icons/phoneIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/positionIcon.svg b/src/assets/icons/positionIcon.svg
new file mode 100644
index 0000000..20e23cc
--- /dev/null
+++ b/src/assets/icons/positionIcon.svg
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/retryIcon.svg b/src/assets/icons/retryIcon.svg
new file mode 100644
index 0000000..8a39e16
--- /dev/null
+++ b/src/assets/icons/retryIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/sadIcon.svg b/src/assets/icons/sadIcon.svg
new file mode 100644
index 0000000..e990775
--- /dev/null
+++ b/src/assets/icons/sadIcon.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/searchIcon.svg b/src/assets/icons/searchIcon.svg
new file mode 100644
index 0000000..38de032
--- /dev/null
+++ b/src/assets/icons/searchIcon.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/smileIcon.svg b/src/assets/icons/smileIcon.svg
new file mode 100644
index 0000000..57d61d5
--- /dev/null
+++ b/src/assets/icons/smileIcon.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/timerIcon.svg b/src/assets/icons/timerIcon.svg
new file mode 100644
index 0000000..087b6eb
--- /dev/null
+++ b/src/assets/icons/timerIcon.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/whiteListIcon.svg b/src/assets/icons/whiteListIcon.svg
new file mode 100644
index 0000000..6adea16
--- /dev/null
+++ b/src/assets/icons/whiteListIcon.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/whiteMapIcon.svg b/src/assets/icons/whiteMapIcon.svg
new file mode 100644
index 0000000..516a862
--- /dev/null
+++ b/src/assets/icons/whiteMapIcon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/components/KakaoMap/KakaoMap.styles.ts b/src/components/KakaoMap/KakaoMap.styles.ts
new file mode 100644
index 0000000..c54b8b9
--- /dev/null
+++ b/src/components/KakaoMap/KakaoMap.styles.ts
@@ -0,0 +1,90 @@
+import styled from "@emotion/styled";
+
+export const Pin = styled.button`
+ min-width: 70px;
+ max-width: 106px;
+ height: 38px;
+ flex-shrink: 0;
+
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 2px 8px;
+ margin: 0 16px;
+
+ ${({ theme }) => theme.text.detail2_bold};
+ color: ${({ theme }) => theme.colors.gray[600]};
+
+ border-radius: 71px;
+ background-color: #fff;
+
+ transition: all 0.2s ease;
+
+ border: 1px solid #00b207;
+ filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
+
+ &::after {
+ content: "";
+ position: absolute;
+ bottom: -14px;
+ left: 50%;
+ transform: translateX(-50%);
+ border-top: 14px solid #fff;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-bottom: 1px solid transparent;
+ }
+
+ &[data-selected="true"] {
+ color: #fff;
+
+ border: 1px solid #fff;
+ background-color: #00b207;
+
+ &::after {
+ border-top: 14px solid #00b207;
+ }
+ }
+`;
+
+export const SearchButton = styled.button`
+ position: absolute;
+ top: 100px;
+ left: 50%;
+ transform: translateX(-50%);
+
+ display: flex;
+ padding: 6px 12px;
+ align-items: center;
+ gap: 4px;
+
+ border-radius: 50px;
+ background: #fff;
+ box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.15), 0px 1px 2px 1px rgba(0, 0, 0, 0.15);
+
+ z-index: 1001;
+`;
+
+export const SearchBtnTxt = styled.p`
+ color: #176d1b;
+
+ ${({ theme }) => theme.text.detail2_reg};
+`;
+
+export const clustererStyles = {
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ width: "80px",
+ height: "80px",
+ padding: "10px",
+ border: "2px solid #fff",
+ borderRadius: "50%",
+ background:
+ "linear-gradient(102deg, rgba(58, 200, 244, 0.90) 10.91%, rgba(94, 155, 243, 0.90) 89.69%)",
+ boxShadow: "0px 2px 18px 3px rgba(0, 0, 0, 0.10)",
+ fontSize: "20px",
+ fontWeight: 600,
+ color: "#fff"
+};
diff --git a/src/components/KakaoMap/KakaoMap.tsx b/src/components/KakaoMap/KakaoMap.tsx
new file mode 100644
index 0000000..883d2ba
--- /dev/null
+++ b/src/components/KakaoMap/KakaoMap.tsx
@@ -0,0 +1,119 @@
+import { useState, useRef, useEffect } from "react";
+import * as S from "./KakaoMap.styles";
+import { Map, MarkerClusterer } from "react-kakao-maps-sdk";
+import { ProductsMarkers } from "../ProductMarker/ProductMarker";
+import { PositionMarker } from "../PositionMarker/PositionMarker";
+import { MapCenter, UserPositionType, ProductType, MapProps } from "./KakaoMap.types";
+import ProductCardForMap from "../ProductCard/ProductCardForMap";
+import Search from "../Search/Search";
+import SearchBar from "@components/SearchBar/SearchBar";
+import { useLocation } from "react-router-dom";
+
+const KakaoMap = ({ result, _setMapCenter }: MapProps) => {
+ if (!result) return null;
+
+ const query = new URLSearchParams(useLocation().search);
+ const latitude = query.get("latitude");
+ const longitude = query.get("longitude");
+ const id = query.get("id");
+
+ const [selectedProductId, setSelectedProductId] = useState(
+ result.length > 0 ? result[0].id : 0
+ );
+ const [mapCenter, setMapCenter] = useState({
+ lat: result.length > 0 ? result[0].latitude : 33.450701,
+ lng: result.length > 0 ? result[0].longitude : 126.570667
+ });
+ const [userPosition, setUserPosition] = useState({
+ lat: null,
+ lng: null,
+ errorMessage: "",
+ isLoading: true
+ });
+
+ const selectedProduct = result.find((product: ProductType) => product.id === selectedProductId);
+
+ const mapRef = useRef(null);
+
+ const initPosition = () => {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ const newPosition = {
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ };
+ console.log("curr position", newPosition);
+ setMapCenter(newPosition);
+ setUserPosition((prev) => ({
+ ...prev,
+ lat: newPosition.lat,
+ lng: newPosition.lng,
+ isLoading: false
+ }));
+ mapRef.current?.setLevel(5);
+ mapRef.current?.setCenter(
+ new kakao.maps.LatLng(position.coords.latitude, position.coords.longitude)
+ );
+ },
+ (err) => {
+ setUserPosition((prev) => ({
+ ...prev,
+ errorMessage: err.message,
+ isLoading: false
+ }));
+ }
+ );
+ } else {
+ setUserPosition((prev) => ({
+ ...prev,
+ errorMessage: "현재 위치를 사용할 수 없습니다.",
+ isLoading: false
+ }));
+ }
+ };
+
+ useEffect(() => {
+ if (!latitude && !longitude) initPosition();
+ else {
+ setMapCenter({ lat: Number(latitude), lng: Number(longitude) });
+ }
+ }, []);
+
+ useEffect(() => {
+ if (id) setSelectedProductId(Number(id));
+ else if (result.length > 0) setSelectedProductId(result[0].id);
+ }, [id, result]);
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default KakaoMap;
diff --git a/src/components/KakaoMap/KakaoMap.types.ts b/src/components/KakaoMap/KakaoMap.types.ts
new file mode 100644
index 0000000..f6aaba7
--- /dev/null
+++ b/src/components/KakaoMap/KakaoMap.types.ts
@@ -0,0 +1,35 @@
+export interface UserPositionType {
+ lat: number | null;
+ lng: number | null;
+ errorMessage: string;
+ isLoading: boolean;
+}
+
+export interface MapCenter {
+ lat: number;
+ lng: number;
+}
+
+export interface UserPositionType {
+ lat: number | null;
+ lng: number | null;
+ errorMessage: string;
+ isLoading: boolean;
+}
+
+export interface ProductType {
+ facility_name: string;
+ id: number;
+ latitude: number;
+ longitude: number;
+ number_addr: string | null;
+ operation_time: string;
+ phone_number: string;
+ road_name_addr: string | null;
+}
+
+export interface MapProps {
+ result: ProductType[] | undefined;
+ total_count: number | undefined;
+ _setMapCenter?: React.Dispatch>;
+}
diff --git a/src/components/MapToggleButton/MapToggleButton.styles.ts b/src/components/MapToggleButton/MapToggleButton.styles.ts
new file mode 100644
index 0000000..20b2f8c
--- /dev/null
+++ b/src/components/MapToggleButton/MapToggleButton.styles.ts
@@ -0,0 +1,34 @@
+import styled from "@emotion/styled";
+
+interface ToggleOptionProps {
+ isActive: boolean;
+}
+
+export const ToggleButtonContainer = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ width: 88px;
+ height: 44px;
+ flex-shrink: 0;
+
+ border-radius: 12px;
+ border: 1px solid ${({ theme }) => theme.colors.gray[300]};
+ background: #fff;
+`;
+
+export const ToggleOption = styled.div`
+ background-color: ${({ isActive }) => (isActive ? "#32CD32" : "#fff")};
+ cursor: pointer;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 42px;
+ height: 38px;
+ flex-shrink: 0;
+
+ border: none;
+ border-radius: 10px;
+`;
diff --git a/src/components/MapToggleButton/MapToggleButton.tsx b/src/components/MapToggleButton/MapToggleButton.tsx
new file mode 100644
index 0000000..d07a9d3
--- /dev/null
+++ b/src/components/MapToggleButton/MapToggleButton.tsx
@@ -0,0 +1,30 @@
+import * as S from "./MapToggleButton.styles";
+import ListIcon from "@assets/icons/listIcon.svg?react";
+import WhiteListIcon from "@assets/icons/whiteListIcon.svg?react";
+import MapIcon from "@assets/icons/mapIcon.svg?react";
+import WhiteMapIcon from "@assets/icons/whiteMapIcon.svg?react";
+import { useNavigate, useLocation } from "react-router-dom";
+
+const ToggleButton = () => {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ return (
+
+ navigate("/seniorCenterList")}
+ >
+ {location.pathname === "/seniorCenterList" ? : }
+
+ navigate("/seniorCenterMap")}
+ >
+ {location.pathname === "/seniorCenterMap" ? : }
+
+
+ );
+};
+
+export default ToggleButton;
diff --git a/src/components/PositionMarker/PositionMarker.tsx b/src/components/PositionMarker/PositionMarker.tsx
new file mode 100644
index 0000000..9dc3865
--- /dev/null
+++ b/src/components/PositionMarker/PositionMarker.tsx
@@ -0,0 +1,21 @@
+import { CustomOverlayMap } from "react-kakao-maps-sdk";
+import { UserPositionType } from "../KakaoMap/KakaoMap.types";
+import PositionIcon from "@assets/icons/positionIcon.svg?react";
+
+interface PositionMarkerProps {
+ position: UserPositionType;
+}
+
+export const PositionMarker = ({ position: { lat, lng, isLoading } }: PositionMarkerProps) => {
+ if (lat && lng) {
+ return (
+
+ {!isLoading && (
+
+
+
+ )}
+
+ );
+ }
+};
diff --git a/src/components/ProductCard/ProductCard.styles.ts b/src/components/ProductCard/ProductCard.styles.ts
new file mode 100644
index 0000000..f81bf47
--- /dev/null
+++ b/src/components/ProductCard/ProductCard.styles.ts
@@ -0,0 +1,107 @@
+import styled from "@emotion/styled";
+
+export const CardContainer = styled.div`
+ position: absolute;
+ bottom: 100px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1001;
+ width: calc(100% - 32px);
+`;
+
+export const CardWrapper = styled.div`
+ position: relative;
+ height: 167px;
+ flex-shrink: 0;
+
+ background: #fff;
+ border-bottom: 1px solid ${({ theme }) => theme.colors.gray[200]};
+
+ cursor: pointer;
+`;
+
+export const MapCardWrapper = styled.div`
+ position: relative;
+ height: 167px;
+ flex-shrink: 0;
+
+ border-radius: 20px;
+ background: #fff;
+ box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1);
+
+ padding: 16px;
+`;
+
+export const ProductTitle = styled.p`
+ color: #151515;
+
+ ${({ theme }) => theme.text.heading4};
+`;
+
+export const AddressWrapper = styled.div`
+ margin-top: 4px;
+`;
+
+export const Address = styled.p`
+ color: ${({ theme }) => theme.colors.gray[700]};
+
+ ${({ theme }) => theme.text.body2_reg};
+`;
+
+export const SubAddress = styled.p`
+ color: ${({ theme }) => theme.colors.gray[500]};
+
+ ${({ theme }) => theme.text.detail1_reg};
+`;
+
+export const InfoWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ margin-top: 11px;
+`;
+
+export const InfoItem = styled.div`
+ display: flex;
+ gap: 8px;
+ justify-content: flex-start;
+ align-items: center;
+`;
+
+export const InfoTitle = styled.p`
+ color: #151515;
+
+ ${({ theme }) => theme.text.body1_bold};
+`;
+
+export const InfoContent = styled.p`
+ color: #151515;
+
+ ${({ theme }) => theme.text.body1_reg};
+`;
+
+export const FriendBtn = styled.button`
+ display: flex;
+ width: 102px;
+ padding: 4px 12px;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+
+ border-radius: 8px;
+ background: #00b207;
+
+ position: absolute;
+ top: 16px;
+ right: 16px;
+`;
+
+export const FriendBtnText = styled.p`
+ color: #fff;
+ ${({ theme }) => theme.text.body1_reg};
+`;
+
+export const FriendBtnBoldTxt = styled.p`
+ color: #fff;
+ ${({ theme }) => theme.text.body1_bold};
+`;
diff --git a/src/components/ProductCard/ProductCard.tsx b/src/components/ProductCard/ProductCard.tsx
new file mode 100644
index 0000000..010844d
--- /dev/null
+++ b/src/components/ProductCard/ProductCard.tsx
@@ -0,0 +1,52 @@
+import * as S from "./ProductCard.styles";
+import TimerIcon from "@assets/icons/timerIcon.svg?react";
+import PhoneIcon from "@assets/icons/phoneIcon.svg?react";
+import { ProductType } from "@components/KakaoMap/KakaoMap.types";
+import { useNavigate } from "react-router-dom";
+
+interface ProductCardProps {
+ selectedProduct: ProductType;
+}
+
+const ProductCardForMap = ({ selectedProduct }: ProductCardProps) => {
+ const {
+ id,
+ facility_name,
+ number_addr,
+ operation_time,
+ phone_number,
+ road_name_addr,
+ latitude,
+ longitude
+ } = selectedProduct;
+ const navigate = useNavigate();
+
+ return (
+
+ navigate(`/seniorCenterMap?latitude=${latitude}&longitude=${longitude}&id=${id}`)
+ }
+ >
+ {facility_name}
+
+ {number_addr}
+ {road_name_addr}
+
+
+
+
+
+ 운영시간
+ {operation_time}
+
+
+
+ 전화번호
+ {phone_number}
+
+
+
+ );
+};
+
+export default ProductCardForMap;
diff --git a/src/components/ProductCard/ProductCardForMap.tsx b/src/components/ProductCard/ProductCardForMap.tsx
new file mode 100644
index 0000000..57a54b5
--- /dev/null
+++ b/src/components/ProductCard/ProductCardForMap.tsx
@@ -0,0 +1,45 @@
+import * as S from "./ProductCard.styles";
+import TimerIcon from "@assets/icons/timerIcon.svg?react";
+import PhoneIcon from "@assets/icons/phoneIcon.svg?react";
+import { ProductType } from "@components/KakaoMap/KakaoMap.types";
+
+interface ProductCardForMapProps {
+ selectedProduct: ProductType;
+}
+
+const ProductCardForMap = ({ selectedProduct }: ProductCardForMapProps) => {
+ const { facility_name, number_addr, operation_time, phone_number, road_name_addr } =
+ selectedProduct;
+
+ return (
+
+
+ {facility_name}
+
+ {number_addr}
+ {road_name_addr}
+
+
+
+
+
+ 운영시간
+ {operation_time}
+
+
+
+ 전화번호
+ {phone_number}
+
+
+
+
+ 내 친구
+ 4명
+
+
+
+ );
+};
+
+export default ProductCardForMap;
diff --git a/src/components/ProductMarker/ProductMarker.tsx b/src/components/ProductMarker/ProductMarker.tsx
new file mode 100644
index 0000000..33854ee
--- /dev/null
+++ b/src/components/ProductMarker/ProductMarker.tsx
@@ -0,0 +1,35 @@
+import { Dispatch, SetStateAction } from "react";
+import * as S from "../KakaoMap/KakaoMap.styles";
+import { CustomOverlayMap, useMap } from "react-kakao-maps-sdk";
+import { ProductType } from "../KakaoMap/KakaoMap.types";
+
+interface ProductMarkersProps {
+ products: ProductType[];
+ selectedProductId: number;
+ setSelectedProductId: Dispatch>;
+}
+
+export const ProductsMarkers = ({
+ products,
+ selectedProductId,
+ setSelectedProductId
+}: ProductMarkersProps) => {
+ const map = useMap();
+
+ const handleSelect = (product: ProductType) => {
+ const { latitude, longitude } = product;
+ setSelectedProductId(product.id);
+ map.panTo(new kakao.maps.LatLng(latitude, longitude));
+ };
+
+ return products.map((product) => (
+
+ handleSelect(product)}
+ >
+ {product.facility_name.slice(0, 7) + "..."}
+
+
+ ));
+};
diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx
new file mode 100644
index 0000000..ee4dcb0
--- /dev/null
+++ b/src/components/Search/Search.tsx
@@ -0,0 +1,46 @@
+import useSearch from "@hooks/useSearch";
+import * as S from "../KakaoMap/KakaoMap.styles";
+import { useSearchParams } from "react-router-dom";
+import { MapCenter } from "@components/KakaoMap/KakaoMap.types";
+import RetryIcon from "@assets/icons/retryIcon.svg?react";
+import { isRefetched } from "@stores/searchStore";
+import { useRecoilState } from "recoil";
+
+interface SearchProps {
+ setMapCenter: React.Dispatch> | undefined;
+}
+
+const Search = ({ setMapCenter }: SearchProps) => {
+ const { position, isMapMoved, setMapMoved } = useSearch();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [, setIsRefetched] = useRecoilState(isRefetched);
+
+ const handleSearch = () => {
+ if (!position) return;
+
+ searchParams.set("swx", position.smallX.toString());
+ searchParams.set("swy", position.smallY.toString());
+ searchParams.set("nex", position.bigX.toString());
+ searchParams.set("ney", position.bigY.toString());
+ setSearchParams(searchParams);
+ setMapMoved(false);
+ if (setMapCenter) {
+ setMapCenter({
+ lat: (position.smallX + position.bigX) / 2,
+ lng: (position.smallY + position.bigY) / 2
+ });
+ setIsRefetched(true);
+ }
+ };
+
+ return (
+ isMapMoved && (
+ handleSearch()}>
+
+ 이 위치에서 경로당 찾기
+
+ )
+ );
+};
+
+export default Search;
diff --git a/src/components/SearchBar/SearchBar.styles.ts b/src/components/SearchBar/SearchBar.styles.ts
new file mode 100644
index 0000000..451f6fb
--- /dev/null
+++ b/src/components/SearchBar/SearchBar.styles.ts
@@ -0,0 +1,41 @@
+import styled from "@emotion/styled";
+
+export const SearchBarContainer = styled.div`
+ width: 100%;
+ position: absolute;
+ top: 33px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1001;
+
+ display: flex;
+ padding: 0 16px;
+ gap: 5px;
+`;
+
+export const InputWrapper = styled.div`
+ width: calc(100% - 88px);
+ height: 44px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex-shrink: 0;
+
+ border-radius: 12px;
+ border: 1px solid ${({ theme }) => theme.colors.gray[300]};
+ background: #fff;
+
+ gap: 3px;
+ padding: 0 16px;
+`;
+
+export const Input = styled.input`
+ width: 100%;
+
+ border: none;
+ border-radius: 12px;
+
+ color: ${({ theme }) => theme.colors.gray[400]};
+
+ ${({ theme }) => theme.text.body1_bold};
+`;
diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx
new file mode 100644
index 0000000..bff3e94
--- /dev/null
+++ b/src/components/SearchBar/SearchBar.tsx
@@ -0,0 +1,23 @@
+import * as S from "./SearchBar.styles";
+import SearchIcon from "@assets/icons/searchIcon.svg?react";
+import MapToggleButton from "@components/MapToggleButton/MapToggleButton";
+import { useNavigate } from "react-router-dom";
+
+const SearchBar = () => {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+ navigate("/seniorCenterSearch")}
+ />
+
+
+
+ );
+};
+
+export default SearchBar;
diff --git a/src/components/Spinner/Spinnter.styles.ts b/src/components/Spinner/Spinnter.styles.ts
index 3b04c1a..56acc62 100644
--- a/src/components/Spinner/Spinnter.styles.ts
+++ b/src/components/Spinner/Spinnter.styles.ts
@@ -6,4 +6,6 @@ export const SpinnerWrapper = styled.div`
align-items: center;
height: 100%;
width: 100%;
+
+ z-index: 10000;
`;
diff --git a/src/hooks/useAroundSeniorCenter.tsx b/src/hooks/useAroundSeniorCenter.tsx
new file mode 100644
index 0000000..89a9a88
--- /dev/null
+++ b/src/hooks/useAroundSeniorCenter.tsx
@@ -0,0 +1,14 @@
+import postAroundSeniorCenter from "@apis/postAroundSeniorCenter";
+import { useQuery } from "@tanstack/react-query";
+
+const useAroundSeniorCenter = (latitude: number, longitude: number) => {
+ return useQuery({
+ queryKey: ["aroundSeniorCenter"],
+ queryFn: async () => {
+ return await postAroundSeniorCenter(latitude, longitude);
+ },
+ enabled: false
+ });
+};
+
+export default useAroundSeniorCenter;
diff --git a/src/hooks/useIntersect.tsx b/src/hooks/useIntersect.tsx
new file mode 100644
index 0000000..9c716be
--- /dev/null
+++ b/src/hooks/useIntersect.tsx
@@ -0,0 +1,30 @@
+import { useCallback, useEffect, useRef } from "react";
+
+type IntersectHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void;
+
+const useIntersect = (
+ onIntersect: IntersectHandler,
+ options?: IntersectionObserverInit
+) => {
+ const ref = useRef(null);
+ const callback = useCallback(
+ (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ onIntersect(entry, observer);
+ }
+ });
+ },
+ [onIntersect]
+ );
+
+ useEffect(() => {
+ if (!ref.current) return;
+ const observer = new IntersectionObserver(callback, options);
+ observer.observe(ref.current);
+ return () => observer.disconnect();
+ }, [ref, options, callback]);
+
+ return ref;
+};
+export default useIntersect;
diff --git a/src/hooks/useSearch.tsx b/src/hooks/useSearch.tsx
new file mode 100644
index 0000000..3763f59
--- /dev/null
+++ b/src/hooks/useSearch.tsx
@@ -0,0 +1,46 @@
+import { useEffect, useState } from "react";
+import { useMap } from "react-kakao-maps-sdk";
+
+interface Position {
+ smallX: number;
+ smallY: number;
+ bigX: number;
+ bigY: number;
+}
+
+const useSearch = () => {
+ const [isMapMoved, setMapMoved] = useState(false);
+ const [position, setPosition] = useState();
+ const map = useMap();
+
+ useEffect(() => {
+ if (!map) return;
+
+ const centerChangedListener = (map: kakao.maps.Map) => {
+ setMapMoved(true);
+
+ const bounds = map.getBounds();
+ const swLatLng = bounds.getSouthWest();
+ const neLatLng = bounds.getNorthEast();
+
+ setPosition({
+ smallX: swLatLng.getLat(),
+ smallY: swLatLng.getLng(),
+ bigX: neLatLng.getLat(),
+ bigY: neLatLng.getLng()
+ });
+ };
+
+ kakao.maps.event.addListener(map, "dragend", () => centerChangedListener(map));
+
+ return () => {
+ if (map) {
+ kakao.maps.event.removeListener(map, "dragend", () => centerChangedListener(map));
+ }
+ };
+ }, [map]);
+
+ return { position, isMapMoved, setMapMoved };
+};
+
+export default useSearch;
diff --git a/src/hooks/useSeniorCenterList.tsx b/src/hooks/useSeniorCenterList.tsx
new file mode 100644
index 0000000..97fe9be
--- /dev/null
+++ b/src/hooks/useSeniorCenterList.tsx
@@ -0,0 +1,25 @@
+import { useSuspenseInfiniteQuery } from "@tanstack/react-query";
+import postSeniorCenterList from "@apis/postSeniorCenterList";
+
+const useSeniorCenterList = (searchValue?: string) => {
+ return useSuspenseInfiniteQuery({
+ queryKey: ["seniorCenterList"],
+ queryFn: async ({ pageParam }) => {
+ return await postSeniorCenterList(pageParam, searchValue ?? "");
+ },
+ initialPageParam: 1,
+ getNextPageParam: (lastPage, allPages) => {
+ const isLastPage = allPages.length >= lastPage.total_count;
+ if (isLastPage) return undefined;
+ return allPages.length + 1;
+ },
+ select: (data) => {
+ const results = data.pages.flatMap((page) => page.result);
+ const total_count = data.pages[0].total_count;
+
+ return { results, total_count };
+ }
+ });
+};
+
+export default useSeniorCenterList;
diff --git a/src/newPages/SeniorCenterList/SeniorCenterList.styles.ts b/src/newPages/SeniorCenterList/SeniorCenterList.styles.ts
new file mode 100644
index 0000000..cb8bb44
--- /dev/null
+++ b/src/newPages/SeniorCenterList/SeniorCenterList.styles.ts
@@ -0,0 +1,9 @@
+import styled from "@emotion/styled";
+
+export const ListContainer = styled.div`
+ overflow: scroll;
+`;
+
+export const Spacer = styled.div`
+ height: 90px;
+`;
diff --git a/src/newPages/SeniorCenterList/SeniorCenterList.tsx b/src/newPages/SeniorCenterList/SeniorCenterList.tsx
new file mode 100644
index 0000000..aeb4b38
--- /dev/null
+++ b/src/newPages/SeniorCenterList/SeniorCenterList.tsx
@@ -0,0 +1,57 @@
+import SearchBar from "@components/SearchBar/SearchBar";
+import { ProductType } from "@components/KakaoMap/KakaoMap.types";
+import ProductCard from "@components/ProductCard/ProductCard";
+import * as S from "./SeniorCenterList.styles";
+import Spinner from "@components/Spinner/Spinner";
+import useAroundSeniorCenter from "@hooks/useAroundSeniorCenter";
+import { useEffect, useState } from "react";
+import { isRefetched } from "@stores/searchStore";
+import { useRecoilState } from "recoil";
+
+interface Position {
+ lat: number;
+ lng: number;
+}
+
+const SeniorCenterList = () => {
+ const [mapCenter, setMapCenter] = useState();
+ const [_isRefetched] = useRecoilState(isRefetched);
+
+ const { data, error, isFetching, refetch } = useAroundSeniorCenter(
+ mapCenter?.lat as number,
+ mapCenter?.lng as number
+ );
+
+ if (error) console.log(error);
+
+ useEffect(() => {
+ if (!_isRefetched && navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition((position) => {
+ const newPosition = {
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ };
+ setMapCenter(newPosition);
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ if (mapCenter) refetch();
+ }, [mapCenter]);
+
+ return (
+
+
+ {data?.result?.map((data: ProductType) => (
+
+ ))}
+
+ {isFetching && }
+
+ );
+};
+
+export default SeniorCenterList;
diff --git a/src/newPages/SeniorCenterMap/SeniorCenterMap.styles.ts b/src/newPages/SeniorCenterMap/SeniorCenterMap.styles.ts
new file mode 100644
index 0000000..feb53cb
--- /dev/null
+++ b/src/newPages/SeniorCenterMap/SeniorCenterMap.styles.ts
@@ -0,0 +1,7 @@
+import styled from "@emotion/styled";
+
+export const MapContainer = styled.div`
+ width: 100%;
+ height: 100%;
+ margin-top: -96px;
+`;
diff --git a/src/newPages/SeniorCenterMap/SeniorCenterMap.tsx b/src/newPages/SeniorCenterMap/SeniorCenterMap.tsx
new file mode 100644
index 0000000..648b52d
--- /dev/null
+++ b/src/newPages/SeniorCenterMap/SeniorCenterMap.tsx
@@ -0,0 +1,58 @@
+import * as S from "./SeniorCenterMap.styles";
+import KakaoMap from "@components/KakaoMap/KakaoMap";
+import Spinner from "@components/Spinner/Spinner";
+import { useEffect, useState } from "react";
+import useAroundSeniorCenter from "@hooks/useAroundSeniorCenter";
+import { useLocation } from "react-router-dom";
+
+interface Position {
+ lat: number;
+ lng: number;
+}
+
+const SeniorCenterMap = () => {
+ const [mapCenter, setMapCenter] = useState();
+ const query = new URLSearchParams(useLocation().search);
+ const latitude = query.get("latitude");
+ const longitude = query.get("longitude");
+
+ const { data, error, isFetching, refetch } = useAroundSeniorCenter(
+ mapCenter?.lat as number,
+ mapCenter?.lng as number
+ );
+
+ if (error) console.log(error);
+
+ useEffect(() => {
+ if (!latitude && !longitude && navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition((position) => {
+ const newPosition = {
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ };
+ setMapCenter(newPosition);
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ if (latitude && longitude) setMapCenter({ lat: Number(latitude), lng: Number(longitude) });
+ }, [latitude, longitude]);
+
+ useEffect(() => {
+ if (mapCenter) refetch();
+ }, [mapCenter]);
+
+ return (
+
+
+ {isFetching && }
+
+ );
+};
+
+export default SeniorCenterMap;
diff --git a/src/newPages/SeniorCenterSearch/SeniorCenterSearch.styles.ts b/src/newPages/SeniorCenterSearch/SeniorCenterSearch.styles.ts
new file mode 100644
index 0000000..bcf4260
--- /dev/null
+++ b/src/newPages/SeniorCenterSearch/SeniorCenterSearch.styles.ts
@@ -0,0 +1,77 @@
+import styled from "@emotion/styled";
+
+export const SearchContainer = styled.div`
+ position: relative;
+ width: 100%;
+ height: 100%;
+`;
+
+export const SearchBarWrapper = styled.div`
+ position: absolute;
+ top: 63px;
+ width: 100%;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+export const SearchBar = styled.input`
+ width: 63%;
+ height: 44px;
+ flex-shrink: 0;
+
+ border-radius: 12px;
+ border: 1px solid ${({ theme }) => theme.colors.gray[300]};
+ background: #fff;
+
+ margin-left: 8px;
+
+ padding: 19px;
+`;
+
+export const SearchBtn = styled.button`
+ color: #151515;
+
+ ${({ theme }) => theme.text.body1_bold}
+
+ margin-left: 10px;
+`;
+
+export const ListWrapper = styled.div`
+ margin-top: 96px;
+ margin-bottom: 90px;
+
+ width: 100%;
+ height: calc(100% - 96px);
+
+ overflow: scroll;
+`;
+
+export const ExpressionWrapper = styled.div`
+ position: absolute;
+ top: 175px;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+
+ gap: 44px;
+`;
+
+export const AlertMsg = styled.div`
+ color: ${({ theme }) => theme.colors.gray[500]};
+ text-align: center;
+
+ ${({ theme }) => theme.text.body1_reg};
+
+ white-space: pre-wrap;
+`;
+
+export const ProductsWrapper = styled.div`
+ position: absolute;
+ top: 95px;
+ width: 100%;
+`;
diff --git a/src/newPages/SeniorCenterSearch/SeniorCenterSearch.tsx b/src/newPages/SeniorCenterSearch/SeniorCenterSearch.tsx
new file mode 100644
index 0000000..5252ced
--- /dev/null
+++ b/src/newPages/SeniorCenterSearch/SeniorCenterSearch.tsx
@@ -0,0 +1,112 @@
+import * as S from "./SeniorCenterSearch.styles";
+import BackArrow from "@assets/icons/backArrow.svg?react";
+import SmileIcon from "@assets/icons/smileIcon.svg?react";
+import SadIcon from "@assets/icons/sadIcon.svg?react";
+import ProductCard from "@components/ProductCard/ProductCard";
+import { useEffect, useRef, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import useSeniorCenterList from "@hooks/useSeniorCenterList";
+import useIntersect from "@hooks/useIntersect";
+import Spinner from "@components/Spinner/Spinner";
+
+const SeniorCenterSearch = () => {
+ const defaultMsg = "지도를 옮겨서\n가까운 경로당을 찾아보세요";
+ const noDataMsg = "검색 결과가 없어요.\n다시 한번 확인해주세요!";
+ const navigate = useNavigate();
+ const searchBarRef = useRef(null);
+ const searchBtnRef = useRef(null);
+
+ const [isSearched, setIsSearched] = useState(false);
+ const [searchValue, setSearchValue] = useState("");
+
+ const { data, error, hasNextPage, isFetching, fetchNextPage, refetch } =
+ useSeniorCenterList(searchValue);
+
+ if (error) console.log(error);
+
+ const ref = useIntersect(async (entry, observer) => {
+ observer.unobserve(entry.target);
+ if (hasNextPage && !isFetching && data.results.length > 0) {
+ await fetchNextPage();
+ }
+ });
+
+ useEffect(() => {
+ if (searchBarRef.current) searchBarRef.current.focus();
+ }, []);
+
+ useEffect(() => {
+ if (searchBarRef.current) {
+ searchBarRef.current.focus();
+
+ const handleKeyPress = (event: KeyboardEvent) => {
+ if (event.key === "Enter") {
+ searchBtnRef.current?.click();
+ }
+ };
+ searchBarRef.current.addEventListener("keypress", handleKeyPress);
+
+ return () => {
+ searchBarRef.current?.removeEventListener("keypress", handleKeyPress);
+ };
+ }
+ }, []);
+
+ useEffect(() => {
+ console.log(data);
+ }, [data]);
+
+ return (
+ <>
+
+ navigate(-1)}>
+
+
+
+ ) =>
+ setSearchValue(event.target.value)
+ }
+ placeholder="검색어를 입력하세요"
+ ref={searchBarRef}
+ />
+ {
+ setIsSearched(true);
+ await refetch();
+ }}
+ ref={searchBtnRef}
+ >
+ 검색
+
+
+
+ {!isSearched ? (
+
+
+ {defaultMsg}
+
+ ) : isSearched && data.results.length === 0 ? (
+
+
+ {noDataMsg}
+
+ ) : (
+ data.results.map((item) => (
+ <>
+
+
+ >
+ ))
+ )}
+ {isFetching && }
+
+ >
+ );
+};
+
+export default SeniorCenterSearch;
diff --git a/src/newPages/tmpData.json b/src/newPages/tmpData.json
new file mode 100644
index 0000000..b747b7b
--- /dev/null
+++ b/src/newPages/tmpData.json
@@ -0,0 +1,144 @@
+{
+ "products": [
+ {
+ "facility_name": "성산LH경로당",
+ "id": 0,
+ "latitude": 33.45033032,
+ "longitude": 126.9108064,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 고성리 1142",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 고성동서로 33"
+ },
+ {
+ "facility_name": "한마음 요양원",
+ "id": 1,
+ "latitude": 33.44543655,
+ "longitude": 126.9134733,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 고성리 1003-3",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 고성오조로 15"
+ },
+ {
+ "facility_name": "성산읍노인복지회관",
+ "id": 2,
+ "latitude": 33.44839048,
+ "longitude": 126.9063725,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 고성리 1549-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 산성효자로 67"
+ },
+ {
+ "facility_name": "삼달1리 경로당",
+ "id": 3,
+ "latitude": 33.37409847,
+ "longitude": 126.8472648,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 삼달리 724-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 삼달로 209"
+ },
+ {
+ "facility_name": "수산1리경로당",
+ "id": 4,
+ "latitude": 33.446746,
+ "longitude": 126.8825851,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 수산리 1199-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 수산서남로 26"
+ },
+ {
+ "facility_name": "수산2리경로당",
+ "id": 5,
+ "latitude": 33.44215479,
+ "longitude": 126.8634756,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 수산리 2432-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 수산서남로 79번길 154"
+ },
+ {
+ "facility_name": "신풍리 사무소",
+ "id": 6,
+ "latitude": 33.36139893,
+ "longitude": 126.8352691,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 신풍리 726-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 신풍상동로 4"
+ },
+ {
+ "facility_name": "오조리사무소",
+ "id": 7,
+ "latitude": 33.46148582,
+ "longitude": 126.914987,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 오조리 155-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 오조로 85"
+ },
+ {
+ "facility_name": "삼달2리 경로당",
+ "id": 8,
+ "latitude": 33.36940843,
+ "longitude": 126.8687192,
+ "number_addr": "제주특별자치도 서귀포시 성산읍 삼달리 29-2",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 서귀포시 성산읍 일주동로 5252"
+ },
+ {
+ "facility_name": "하도리 경로당",
+ "id": 9,
+ "latitude": 33.51766239,
+ "longitude": 126.8836615,
+ "number_addr": "제주특별자치도 제주시 구좌읍 하도리 1318-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 제주시 구좌읍 일주동로 3425"
+ },
+ {
+ "facility_name": "상한동 복지회관",
+ "id": 10,
+ "latitude": 33.53480165,
+ "longitude": 126.8233205,
+ "number_addr": "제주특별자치도 제주시 구좌읍 한동리 947",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 제주시 구좌읍 한동로 64"
+ },
+ {
+ "facility_name": "세화요양원",
+ "id": 11,
+ "latitude": 33.52197875,
+ "longitude": 126.8528362,
+ "number_addr": "제주특별자치도 제주시 구좌읍 세화리 1558-1",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 제주시 구좌읍 세화서길 7-1"
+ },
+ {
+ "facility_name": "평대리 경로당",
+ "id": 12,
+ "latitude": 33.52838728,
+ "longitude": 126.8427599684,
+ "number_addr": "제주특별자치도 제주시 구좌읍 평대리 733",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 제주시 구좌읍 일주동로 3007"
+ },
+ {
+ "facility_name": "평대리동동 경로당",
+ "id": 13,
+ "latitude": 33.5243484,
+ "longitude": 126.8487004,
+ "number_addr": "제주특별자치도 제주시 구좌읍 평대리 362",
+ "operation_time": "월~금 09:00~18:00",
+ "phone_number": "064-710-6413",
+ "road_name_addr": "제주특별자치도 제주시 구좌읍 일주동로 3080"
+ }
+ ]
+}
diff --git a/src/routes/router.tsx b/src/routes/router.tsx
index 9c849eb..ae76430 100644
--- a/src/routes/router.tsx
+++ b/src/routes/router.tsx
@@ -10,7 +10,9 @@ import OtherCollection from "../pages/otherCollection/OtherCollection";
import WriteDiary from "../pages/writeDiary/WriteDiary";
import NewBottom from "@components/NewBottomNav/NewBottomNav";
import Header from "@components/HeaderNav/HeaderNav";
-import MyInfo from "../newPages/myInfo/MyInfo";
+import SeniorCenterMap from "@newPages/SeniorCenterMap/SeniorCenterMap";
+import SeniorCenterList from "@newPages/SeniorCenterList/SeniorCenterList";
+import SeniorCenterSearch from "@newPages/SeniorCenterSearch/SeniorCenterSearch";
const router = createBrowserRouter([
{
@@ -98,6 +100,32 @@ const router = createBrowserRouter([
)
},
{
+ path: "/seniorCenterMap",
+ element: (
+ <>
+
+
+ >
+ )
+ },
+ {
+ path: "/seniorCenterList",
+ element: (
+ <>
+
+
+ >
+ )
+ },
+ {
+ path: "/seniorCenterSearch",
+ element: (
+ <>
+
+
+ >
+ )
+ },
path: "/lesson",
element: (
<>
diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts
new file mode 100644
index 0000000..3cdaa36
--- /dev/null
+++ b/src/stores/searchStore.ts
@@ -0,0 +1,6 @@
+import { atom } from "recoil";
+
+export const isRefetched = atom({
+ key: "diaryState",
+ default: false
+});
diff --git a/tsconfig.json b/tsconfig.json
index b4c36e2..5cdc3d7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,5 +35,6 @@
"noFallthroughCasesInSwitch": true
},
"include": ["src", "src/custom.d.ts"],
- "references": [{ "path": "./tsconfig.node.json" }]
+ "references": [{ "path": "./tsconfig.node.json" }],
+ "types": ["kakao.maps.d.ts"]
}