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
3 changes: 1 addition & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
VITE_NCP_API_KEY_ID=pvngw60boi
VITE_NCP_API_KEY=pQerQ8RH43mXRUitA0yZS0fv8az2y3AR2NAJ1GR3
VITE_KAKAO_REST_API_KEY=f5171fb698d216fdffae9815161287c6
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_KAKAO_REST_API_KEY=f5171fb698d216fdffae9815161287c6
25 changes: 12 additions & 13 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@
<link rel="icon" type="image/svg+xml" href="Logo-Center.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>산재보험 가이드</title>
</head>
<body>
<div id="root"></div>

<script
type="text/javascript"
src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=pvngw60boi&submodules=geocoder&callback=__naverMapReady"
defer
></script>
<!-- 🔥 Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-QNY1YY9K99"></script>
<script>
window.__naverMapReady = () => {
console.log("Naver Maps ready");
};
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-QNY1YY9K99');
</script>
<script type="module" src="/src/main.tsx"></script>
<!-- 🔥 End Google Analytics -->

</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
</html>
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@svgr/rollup": "^8.1.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@types/navermaps": "^3.9.1",
"@types/node": "^24.8.1",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
Expand Down
7 changes: 7 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { FormDataProvider } from "./contexts/FormDataContext";
import { LocalizationProvider } from "./contexts/LocalizationContext";
import usePageTracking from "./hooks/usePageTracking";

import HomePage from "./pages/HomePage/HomePage";
import CasePage from "./pages/CasePage/CasePage";
import CaseDetailPage from "./pages/CasePage/CaseDetailPage";
Expand Down Expand Up @@ -33,12 +35,17 @@ import SplashPage from "./pages/SplashPage/SplashPage";
// import EditPage from "./pages/MyPage/EditPage";
// import DataPage from "./pages/MyPage/DataPage";
// import LoginPage from "./pages/LoginPage/LoginPage";
function PageTrackerWrapper() {
usePageTracking(); // Router 내부에서 호출됨
return null;
}

function App() {
return (
<Router>
<LocalizationProvider>
<FormDataProvider>
<PageTrackerWrapper />
<Routes>
<Route path="/" element={<SplashPage />} />
<Route path="/lang" element={<LangPage />} />
Expand Down
8 changes: 8 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//usePageTracking.ts 고려
declare global {
interface Window {
gtag?: (...args: any[]) => void;
}
}

export {};
15 changes: 15 additions & 0 deletions src/hooks/usePageTracking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//SPA 페이지 이동 추적 코드
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

export default function usePageTracking() {
const location = useLocation();

useEffect(() => {
if (typeof window.gtag !== "undefined") {
window.gtag("event", "page_view", {
page_path: location.pathname + location.search,
});
}
}, [location]);
}
143 changes: 82 additions & 61 deletions src/pages/Medi-carePage/Medi-address-1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,34 @@ import "./Medi-address-1.css";
import { useState, useCallback } from "react";
import { useNavigate, useLocation } from "react-router-dom";

// 네이버 지오코딩 API 응답 타입 정의
// 카카오 주소 검색 응답 타입
interface Address {
roadAddress: string;
jibunAddress: string;
englishAddress: string;
englishAddress?: string;
}

interface GeocodeResponse {
status: string;
meta: {
totalCount: number;
page: number;
count: number;
};
addresses: Address[];
documents: {
address_name: string;
road_address: {
address_name: string;
} | null;
address: {
address_name: string;
} | null;
}[];
}

const MediAddress1 = () => {
const navigate = useNavigate();
const location = useLocation();
const { t } = useLocalization();
const { returnPath, returnToStep, fieldName } = location.state || {
returnPath: '/medicare-guide-flow',

const { returnPath, returnToStep, fieldName } = location.state || {
returnPath: "/medicare-guide-flow",
returnToStep: 1,
fieldName: 'address'
fieldName: "address",
};

const [searchQuery, setSearchQuery] = useState("");
Expand All @@ -38,57 +41,66 @@ const MediAddress1 = () => {
const [isAddressConfirmed, setIsAddressConfirmed] = useState(false);
const [detailAddress, setDetailAddress] = useState("");

// API 호출 함수
// 카카오 지오코딩
const searchAddress = useCallback(async (query: string) => {
if (!query.trim()) {
setAddresses([]);
return;
}

try {
// 프록시 경로로 변경
const response = await fetch(
`/api/geocode?query=${encodeURIComponent(query)}`,
`https://dapi.kakao.com/v2/local/search/address.json?query=${encodeURIComponent(
query
)}`,
{
method: 'GET',
method: "GET",
headers: {
'Accept': 'application/json'
}
Authorization: `KakaoAK ${
import.meta.env.VITE_KAKAO_REST_API_KEY
}`,
},
}
);

const data: GeocodeResponse = await response.json();

if (data.status === 'OK' && data.addresses) {
setAddresses(data.addresses);

if (data.documents && data.documents.length > 0) {
const parsed = data.documents.map((doc) => ({
roadAddress: doc.road_address?.address_name ?? "",
jibunAddress: doc.address?.address_name ?? "",
englishAddress: "",
}));

setAddresses(parsed);
} else {
setAddresses([]);
}
} catch (error) {
console.error('주소 검색 실패:', error);
console.error("주소 검색 실패:", error);
setAddresses([]);
}
}, []);

// 입력 핸들러 (디바운싱 적용)
// 입력 디바운스
let debounceTimer: number | undefined;
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchQuery(value);

// 간단한 디바운싱 (500ms 후 검색)
const timeoutId = setTimeout(() => {

if (debounceTimer) clearTimeout(debounceTimer);

debounceTimer = window.setTimeout(() => {
searchAddress(value);
}, 500);

return () => clearTimeout(timeoutId);
};

// 주소 선택 핸들러
// 주소 선택
const handleSelectAddress = (address: Address) => {
setSelectedAddress(address);
};

// 주소 입력칸 클릭 핸들러 (주소 확정 후 다시 검색)
// 주소 확정 후 다시 클릭하면 초기화
const handleAddressInputClick = () => {
if (isAddressConfirmed) {
setIsAddressConfirmed(false);
Expand All @@ -98,93 +110,102 @@ const MediAddress1 = () => {
}
};

// 다음 버튼
const handleNext = () => {
if (!isAddressConfirmed && selectedAddress) {
// 첫 번째 확인: 주소 확정
setSearchQuery(selectedAddress.roadAddress);
setAddresses([]);
setIsAddressConfirmed(true);
} else if (isAddressConfirmed) {
// 두 번째 확인: 최종 완료 - 원래 페이지로 돌아가기
const fullAddress = detailAddress
const fullAddress = detailAddress
? `${searchQuery} ${detailAddress}`
: searchQuery;

navigate(returnPath, {
state: {
[fieldName]: fullAddress,
returnToStep: returnToStep // step 정보 전달
}
returnToStep: returnToStep,
},
});
}
};

const handleBack = () => {
// 뒤로가기 로직 - 원래 페이지로 돌아가기
navigate(returnPath);
};

// 상세 주소 입력 핸들러
const handleDetailAddressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleDetailAddressChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setDetailAddress(e.target.value);
};

return (
<div className="app">
<Header title={t('guidePractice')} onBack={handleBack} showHomebtn={true}/>
<Header
title={t("guidePractice")}
onBack={handleBack}
showHomebtn={true}
/>

<div className="medi-guide-title">
<h2>{t('applyEnterAddr')}</h2>
<h2>{t("applyEnterAddr")}</h2>
</div>

<div className="medi-guide-content">
<div className="medi-addr-input">
<input
className="medi-addr-input-text"
type="text"
placeholder={t('applyLookBuilding')}
<input
className="medi-addr-input-text"
type="text"
placeholder={t("applyLookBuilding")}
value={searchQuery}
onChange={handleInputChange}
onClick={handleAddressInputClick}
readOnly={isAddressConfirmed}
/>
</div>
{/* 주소 검색 결과 - 주소 확정 전에만 표시 */}

{/* 검색 결과 */}
{!isAddressConfirmed && (
<div className="medi-addr-result">
{addresses.map((address, index) => (
<div
<div
key={index}
className={`medi-addr-result-box ${selectedAddress === address ? 'selected' : ''}`}
className={`medi-addr-result-box ${
selectedAddress === address ? "selected" : ""
}`}
onClick={() => handleSelectAddress(address)}
>
<div className="road-address">{address.roadAddress}</div>
<div className="jibun-address">{address.jibunAddress}</div>
<div className="road-address">
{address.roadAddress}
</div>
<div className="jibun-address">
{address.jibunAddress}
</div>
</div>
))}
</div>
)}

{/* 상세 주소 입력 - 주소 확정 후에만 표시 */}
{/* 상세 주소 입력 */}
{isAddressConfirmed && (
<div className="medi-addr-input medi-detail-addr">
<input
className="medi-addr-input-text"
type="text"
placeholder={t('applySpecificAddr')}
<input
className="medi-addr-input-text"
type="text"
placeholder={t("applySpecificAddr")}
value={detailAddress}
onChange={handleDetailAddressChange}
/>
</div>
)}
</div>

<div className="save-button-container">
<ContinueButton
text={t('applyCheck')}
onClick={handleNext}
/>
<ContinueButton text={t("applyCheck")} onClick={handleNext} />
</div>
</div>
);
};

export default MediAddress1;
export default MediAddress1;
Loading