From ff4ac26a49807b391da8bcc9deccf06984756c25 Mon Sep 17 00:00:00 2001
From: HSCHEOL
Date: Mon, 9 Feb 2026 16:25:47 +0900
Subject: [PATCH 1/9] =?UTF-8?q?feat:AI=20=EC=9A=94=EC=95=BD=20=ED=85=8D?=
=?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EB=A7=88=ED=81=AC=20=EB=8B=A4?=
=?UTF-8?q?=EC=9A=B4=EC=9C=BC=EB=A1=9C=20=EB=B0=9B=EC=95=84=20Parsing=20?=
=?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B2=83=EC=9C=BC=EB=A1=9C=20=EB=B3=80?=
=?UTF-8?q?=EA=B2=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../list/list_section/CurationSection.tsx | 73 +-
app/components/mypage/CurationComparison.tsx | 31 +-
package.json | 2 +
yarn.lock | 754 +++++++++++++++++-
4 files changed, 826 insertions(+), 34 deletions(-)
diff --git a/app/components/list/list_section/CurationSection.tsx b/app/components/list/list_section/CurationSection.tsx
index 6919338..caced3e 100644
--- a/app/components/list/list_section/CurationSection.tsx
+++ b/app/components/list/list_section/CurationSection.tsx
@@ -2,6 +2,8 @@
import Image from "next/image";
import React, { useState } from "react";
+import ReactMarkdown from "react-markdown";
+import rehypeRaw from "rehype-raw";
import { useMapModeStore } from "../../../store/mapModeStore";
const CATEGORY_TOOLTIPS: Record = {
@@ -58,35 +60,48 @@ const CurationSection = ({
D.HOME 요약
-
-
- {isAiLoading ? (
-
- AI가 매물을 분석하고 있습니다...
-
- ) : aiSummary ? (
- aiSummary
- ) : sortedMatchedConditions.length > 0 ? (
- <>
- 해당 지역의{" "}
- {sortedMatchedConditions.slice(0, 2).map((label, i) => (
-
-
- {label} 점수
-
- {i === 0 && sortedMatchedConditions.length > 1
- ? " 및 "
- : ""}
-
- ))}{" "}
- 높아 거주 만족도가 기대되는 추천 매물입니다!
- >
- ) : (
-
- AI가 매물을 분석하고 있습니다...
-
- )}
-
+
+ {isAiLoading ? (
+
+ AI가 매물을 분석하고 있습니다...
+
+ ) : aiSummary ? (
+
(
+ {children}
+ ),
+ strong: ({ children }) => (
+
+ {children}
+
+ ),
+ ul: ({ children }) => (
+
+ ),
+ li: ({ children }) => {children},
+ div: ({ children }) => {children}
,
+ }}
+ >
+ {aiSummary}
+
+ ) : sortedMatchedConditions.length > 0 ? (
+
+ 해당 지역의{" "}
+ {sortedMatchedConditions.slice(0, 2).map((label, i) => (
+
+ {label} 점수
+ {i === 0 && sortedMatchedConditions.length > 1 ? " 및 " : ""}
+
+ ))}{" "}
+ 높아 거주 만족도가 기대되는 추천 매물입니다!
+
+ ) : (
+
+ AI가 매물을 분석하고 있습니다...
+
+ )}
diff --git a/app/components/mypage/CurationComparison.tsx b/app/components/mypage/CurationComparison.tsx
index d885693..4d40821 100644
--- a/app/components/mypage/CurationComparison.tsx
+++ b/app/components/mypage/CurationComparison.tsx
@@ -2,6 +2,8 @@
import React from "react";
import Image from "next/image";
+import ReactMarkdown from "react-markdown";
+import rehypeRaw from "rehype-raw";
import { cn } from "@/app/lib/utils";
import CompareTag, { TagType } from "./CompareTag";
@@ -129,9 +131,32 @@ const CurationComparison = ({ data }: CurationComparisonProps) => {
AI 요약 리포트
-
{item.tags.map((tag) => (
diff --git a/package.json b/package.json
index 9529a27..64faeaf 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,8 @@
"react-dom": "19.2.3",
"react-hook-form": "^7.71.1",
"react-kakao-maps-sdk": "^1.2.0",
+ "react-markdown": "^10.1.0",
+ "rehype-raw": "^7.0.0",
"tailwind-merge": "^3.4.0",
"zod": "^4.3.6",
"zustand": "^5.0.11"
diff --git a/yarn.lock b/yarn.lock
index 63d0d59..3780892 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1506,6 +1506,13 @@
"@types/d3-transition" "*"
"@types/d3-zoom" "*"
+"@types/debug@^4.0.0":
+ version "4.1.12"
+ resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
+ integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==
+ dependencies:
+ "@types/ms" "*"
+
"@types/es-aggregate-error@^1.0.2":
version "1.0.6"
resolved "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.6.tgz"
@@ -1513,7 +1520,14 @@
dependencies:
"@types/node" "*"
-"@types/estree@^1.0.6":
+"@types/estree-jsx@^1.0.0":
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18"
+ integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==
+ dependencies:
+ "@types/estree" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6":
version "1.0.8"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
@@ -1523,6 +1537,13 @@
resolved "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz"
integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==
+"@types/hast@^3.0.0":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
+ integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
+ dependencies:
+ "@types/unist" "*"
+
"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.7":
version "7.0.15"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
@@ -1533,6 +1554,18 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+"@types/mdast@^4.0.0":
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
+ integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
+ dependencies:
+ "@types/unist" "*"
+
+"@types/ms@*":
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
+ integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
+
"@types/node@*":
version "25.0.10"
resolved "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz"
@@ -1559,6 +1592,16 @@
dependencies:
csstype "^3.2.2"
+"@types/unist@*", "@types/unist@^3.0.0":
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
+ integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
+
+"@types/unist@^2.0.0":
+ version "2.0.11"
+ resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
+ integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
+
"@types/urijs@^1.19.19":
version "1.19.26"
resolved "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.26.tgz"
@@ -1660,6 +1703,11 @@
"@typescript-eslint/types" "8.53.1"
eslint-visitor-keys "^4.2.1"
+"@ungap/structured-clone@^1.0.0":
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
+ integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+
"@unrs/resolver-binding-android-arm-eabi@1.11.1":
version "1.11.1"
resolved "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81"
@@ -1988,6 +2036,11 @@ axobject-query@^4.1.0:
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz"
integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
+bail@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d"
+ integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@@ -2082,6 +2135,11 @@ caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001759:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz"
integrity sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==
+ccount@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
+ integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
+
chalk@^4.0.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
@@ -2090,6 +2148,26 @@ chalk@^4.0.0, chalk@^4.1.2:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+character-entities-html4@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
+ integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
+
+character-entities-legacy@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
+ integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+
+character-entities@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
+ integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
+
+character-reference-invalid@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
+ integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
+
chokidar@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz"
@@ -2143,6 +2221,11 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
+comma-separated-tokens@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
+ integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
+
commander@7:
version "7.2.0"
resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz"
@@ -2460,13 +2543,20 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3:
+debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3:
version "4.4.3"
resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
+decode-named-character-reference@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f"
+ integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==
+ dependencies:
+ character-entities "^2.0.0"
+
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
@@ -2507,11 +2597,23 @@ dependency-graph@0.11.0, dependency-graph@~0.11.0:
resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz"
integrity sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==
+dequal@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
+ integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+
detect-libc@^2.0.3, detect-libc@^2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz"
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
+devlop@^1.0.0, devlop@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018"
+ integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
+ dependencies:
+ dequal "^2.0.0"
+
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz"
@@ -2566,6 +2668,11 @@ enquirer@^2.4.1:
ansi-colors "^4.1.1"
strip-ansi "^6.0.1"
+entities@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694"
+ integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
+
es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.1:
version "1.24.1"
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz"
@@ -2960,6 +3067,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+estree-util-is-identifier-name@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd"
+ integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==
+
esutils@2.0.3, esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
@@ -2985,6 +3097,11 @@ execa@^5.1.1:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
+extend@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
@@ -3309,6 +3426,98 @@ hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
+hast-util-from-parse5@^8.0.0:
+ version "8.0.3"
+ resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e"
+ integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ devlop "^1.0.0"
+ hastscript "^9.0.0"
+ property-information "^7.0.0"
+ vfile "^6.0.0"
+ vfile-location "^5.0.0"
+ web-namespaces "^2.0.0"
+
+hast-util-parse-selector@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27"
+ integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hast-util-raw@^9.0.0:
+ version "9.1.0"
+ resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e"
+ integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ hast-util-from-parse5 "^8.0.0"
+ hast-util-to-parse5 "^8.0.0"
+ html-void-elements "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ parse5 "^7.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-to-jsx-runtime@^2.0.0:
+ version "2.3.6"
+ resolved "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98"
+ integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/unist" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ estree-util-is-identifier-name "^3.0.0"
+ hast-util-whitespace "^3.0.0"
+ mdast-util-mdx-expression "^2.0.0"
+ mdast-util-mdx-jsx "^3.0.0"
+ mdast-util-mdxjs-esm "^2.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ style-to-js "^1.0.0"
+ unist-util-position "^5.0.0"
+ vfile-message "^4.0.0"
+
+hast-util-to-parse5@^8.0.0:
+ version "8.0.1"
+ resolved "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz#95aa391cc0514b4951418d01c883d1038af42f5d"
+ integrity sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ devlop "^1.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+ web-namespaces "^2.0.0"
+ zwitch "^2.0.0"
+
+hast-util-whitespace@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621"
+ integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+
+hastscript@^9.0.0:
+ version "9.0.1"
+ resolved "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff"
+ integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ comma-separated-tokens "^2.0.0"
+ hast-util-parse-selector "^4.0.0"
+ property-information "^7.0.0"
+ space-separated-tokens "^2.0.0"
+
hermes-estree@0.25.1:
version "0.25.1"
resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz"
@@ -3321,6 +3530,16 @@ hermes-parser@^0.25.1:
dependencies:
hermes-estree "0.25.1"
+html-url-attributes@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87"
+ integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==
+
+html-void-elements@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
+ integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
+
http2-client@^1.2.5:
version "1.3.5"
resolved "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz"
@@ -3371,6 +3590,11 @@ inflected@^2.1.0:
resolved "https://registry.npmjs.org/inflected/-/inflected-2.1.0.tgz"
integrity sha512-hAEKNxvHf2Iq3H60oMBHkB4wl5jn3TPF3+fXek/sRwAB5gP9xWs4r7aweSF95f99HFoz69pnZTcu8f0SIHV18w==
+inline-style-parser@0.2.7:
+ version "0.2.7"
+ resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909"
+ integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==
+
internal-slot@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz"
@@ -3385,6 +3609,19 @@ internal-slot@^1.1.0:
resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz"
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
+is-alphabetical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b"
+ integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
+
+is-alphanumerical@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875"
+ integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
+ dependencies:
+ is-alphabetical "^2.0.0"
+ is-decimal "^2.0.0"
+
is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz"
@@ -3463,6 +3700,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0:
call-bound "^1.0.2"
has-tostringtag "^1.0.2"
+is-decimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7"
+ integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
+
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
@@ -3498,6 +3740,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-hexadecimal@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027"
+ integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
+
is-map@^2.0.3:
version "2.0.3"
resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz"
@@ -3521,6 +3768,11 @@ is-number@^7.0.0:
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+is-plain-obj@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
+ integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+
is-regex@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz"
@@ -3894,6 +4146,11 @@ loglevel@^1.9.2:
resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz"
integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==
+longest-streak@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
+ integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
+
loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
@@ -3925,6 +4182,111 @@ math-intrinsics@^1.1.0:
resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+mdast-util-from-markdown@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a"
+ integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark "^4.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+mdast-util-mdx-expression@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096"
+ integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-mdx-jsx@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d"
+ integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ ccount "^2.0.0"
+ devlop "^1.1.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+ parse-entities "^4.0.0"
+ stringify-entities "^4.0.0"
+ unist-util-stringify-position "^4.0.0"
+ vfile-message "^4.0.0"
+
+mdast-util-mdxjs-esm@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97"
+ integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==
+ dependencies:
+ "@types/estree-jsx" "^1.0.0"
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ mdast-util-to-markdown "^2.0.0"
+
+mdast-util-phrasing@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3"
+ integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ unist-util-is "^6.0.0"
+
+mdast-util-to-hast@^13.0.0:
+ version "13.2.1"
+ resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053"
+ integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ "@ungap/structured-clone" "^1.0.0"
+ devlop "^1.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ trim-lines "^3.0.0"
+ unist-util-position "^5.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
+mdast-util-to-markdown@^2.0.0:
+ version "2.1.2"
+ resolved "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b"
+ integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ "@types/unist" "^3.0.0"
+ longest-streak "^3.0.0"
+ mdast-util-phrasing "^4.0.0"
+ mdast-util-to-string "^4.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-decode-string "^2.0.0"
+ unist-util-visit "^5.0.0"
+ zwitch "^2.0.0"
+
+mdast-util-to-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814"
+ integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
@@ -3935,6 +4297,200 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+micromark-core-commonmark@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4"
+ integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-factory-destination "^2.0.0"
+ micromark-factory-label "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-factory-title "^2.0.0"
+ micromark-factory-whitespace "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-classify-character "^2.0.0"
+ micromark-util-html-tag-name "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-destination@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639"
+ integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-label@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1"
+ integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-space@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc"
+ integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-title@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94"
+ integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-factory-whitespace@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1"
+ integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==
+ dependencies:
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-character@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6"
+ integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-chunked@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051"
+ integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-classify-character@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629"
+ integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-combine-extensions@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9"
+ integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==
+ dependencies:
+ micromark-util-chunked "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-decode-numeric-character-reference@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5"
+ integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-decode-string@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2"
+ integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==
+ dependencies:
+ decode-named-character-reference "^1.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-encode@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8"
+ integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==
+
+micromark-util-html-tag-name@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825"
+ integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==
+
+micromark-util-normalize-identifier@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d"
+ integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==
+ dependencies:
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-resolve-all@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b"
+ integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==
+ dependencies:
+ micromark-util-types "^2.0.0"
+
+micromark-util-sanitize-uri@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7"
+ integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==
+ dependencies:
+ micromark-util-character "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+
+micromark-util-subtokenize@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee"
+ integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==
+ dependencies:
+ devlop "^1.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
+micromark-util-symbol@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8"
+ integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
+
+micromark-util-types@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e"
+ integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
+
+micromark@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb"
+ integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==
+ dependencies:
+ "@types/debug" "^4.0.0"
+ debug "^4.0.0"
+ decode-named-character-reference "^1.0.0"
+ devlop "^1.0.0"
+ micromark-core-commonmark "^2.0.0"
+ micromark-factory-space "^2.0.0"
+ micromark-util-character "^2.0.0"
+ micromark-util-chunked "^2.0.0"
+ micromark-util-combine-extensions "^2.0.0"
+ micromark-util-decode-numeric-character-reference "^2.0.0"
+ micromark-util-encode "^2.0.0"
+ micromark-util-normalize-identifier "^2.0.0"
+ micromark-util-resolve-all "^2.0.0"
+ micromark-util-sanitize-uri "^2.0.0"
+ micromark-util-subtokenize "^2.0.0"
+ micromark-util-symbol "^2.0.0"
+ micromark-util-types "^2.0.0"
+
micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz"
@@ -4294,6 +4850,26 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
+parse-entities@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159"
+ integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ character-entities-legacy "^3.0.0"
+ character-reference-invalid "^2.0.0"
+ decode-named-character-reference "^1.0.0"
+ is-alphanumerical "^2.0.0"
+ is-decimal "^2.0.0"
+ is-hexadecimal "^2.0.0"
+
+parse5@^7.0.0:
+ version "7.3.0"
+ resolved "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05"
+ integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==
+ dependencies:
+ entities "^6.0.0"
+
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
@@ -4371,6 +4947,11 @@ prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
+property-information@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d"
+ integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==
+
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -4411,6 +4992,23 @@ react-kakao-maps-sdk@^1.2.0:
"@babel/runtime" "^7.22.15"
kakao.maps.d.ts "^0.1.39"
+react-markdown@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca"
+ integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ devlop "^1.0.0"
+ hast-util-to-jsx-runtime "^2.0.0"
+ html-url-attributes "^3.0.0"
+ mdast-util-to-hast "^13.0.0"
+ remark-parse "^11.0.0"
+ remark-rehype "^11.0.0"
+ unified "^11.0.0"
+ unist-util-visit "^5.0.0"
+ vfile "^6.0.0"
+
react@19.2.3:
version "19.2.3"
resolved "https://registry.npmjs.org/react/-/react-19.2.3.tgz"
@@ -4454,6 +5052,36 @@ regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4:
gopd "^1.2.0"
set-function-name "^2.0.2"
+rehype-raw@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4"
+ integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ hast-util-raw "^9.0.0"
+ vfile "^6.0.0"
+
+remark-parse@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1"
+ integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==
+ dependencies:
+ "@types/mdast" "^4.0.0"
+ mdast-util-from-markdown "^2.0.0"
+ micromark-util-types "^2.0.0"
+ unified "^11.0.0"
+
+remark-rehype@^11.0.0:
+ version "11.1.2"
+ resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37"
+ integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==
+ dependencies:
+ "@types/hast" "^3.0.0"
+ "@types/mdast" "^4.0.0"
+ mdast-util-to-hast "^13.0.0"
+ unified "^11.0.0"
+ vfile "^6.0.0"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
@@ -4750,6 +5378,11 @@ source-map-js@^1.0.2, source-map-js@^1.2.1:
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+space-separated-tokens@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
+ integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
+
stable-hash@^0.0.5:
version "0.0.5"
resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz"
@@ -4845,6 +5478,14 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
+stringify-entities@^4.0.0:
+ version "4.0.4"
+ resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3"
+ integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
+ dependencies:
+ character-entities-html4 "^2.0.0"
+ character-entities-legacy "^3.0.0"
+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@@ -4867,6 +5508,20 @@ strip-json-comments@^3.1.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+style-to-js@^1.0.0:
+ version "1.1.21"
+ resolved "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d"
+ integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==
+ dependencies:
+ style-to-object "1.0.14"
+
+style-to-object@1.0.14:
+ version "1.0.14"
+ resolved "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611"
+ integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==
+ dependencies:
+ inline-style-parser "0.2.7"
+
styled-jsx@5.1.6:
version "5.1.6"
resolved "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz"
@@ -4938,6 +5593,16 @@ tr46@~0.0.3:
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+trim-lines@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
+ integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
+
+trough@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
+ integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
+
ts-api-utils@^2.4.0:
version "2.4.0"
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz"
@@ -5055,6 +5720,57 @@ undici-types@~7.16.0:
resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz"
integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
+unified@^11.0.0:
+ version "11.0.5"
+ resolved "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1"
+ integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ bail "^2.0.0"
+ devlop "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^4.0.0"
+ trough "^2.0.0"
+ vfile "^6.0.0"
+
+unist-util-is@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9"
+ integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-position@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4"
+ integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-stringify-position@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2"
+ integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+
+unist-util-visit-parents@^6.0.0:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02"
+ integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+
+unist-util-visit@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468"
+ integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-is "^6.0.0"
+ unist-util-visit-parents "^6.0.0"
+
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz"
@@ -5117,6 +5833,35 @@ validator@^13.15.23:
resolved "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz"
integrity sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==
+vfile-location@^5.0.0:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3"
+ integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile "^6.0.0"
+
+vfile-message@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4"
+ integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ unist-util-stringify-position "^4.0.0"
+
+vfile@^6.0.0:
+ version "6.0.3"
+ resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab"
+ integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==
+ dependencies:
+ "@types/unist" "^3.0.0"
+ vfile-message "^4.0.0"
+
+web-namespaces@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
+ integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
+
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
@@ -5261,3 +6006,8 @@ zustand@^5.0.11:
version "5.0.11"
resolved "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494"
integrity sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==
+
+zwitch@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
+ integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
From 9bd4b1f7afa6934eb65c3de81ef0732535b19ebb Mon Sep 17 00:00:00 2001
From: HSCHEOL
Date: Mon, 9 Feb 2026 17:03:50 +0900
Subject: [PATCH 2/9] create:swagger orval hook update
---
.../environment-controller.ts | 68 ++++++++++++++++-
.../api/generated/model/detailPropertyDTO.ts | 2 +
.../generated/model/environmentTotalDTO.ts | 17 +++++
.../model/getEnvironmentTotalParams.ts | 12 +++
shared/api/generated/model/index.ts | 8 ++
.../api/generated/model/particulateMatter.ts | 12 +++
.../generated/model/policeModalResponse.ts | 14 ++++
shared/api/generated/model/propertyListDTO.ts | 5 +-
shared/api/generated/model/propertyScore.ts | 15 ++++
.../model/responseDTOEnvironmentTotalDTO.ts | 15 ++++
.../model/responseDTOSafetyModalResponse.ts | 15 ++++
.../generated/model/safetyModalResponse.ts | 15 ++++
.../model/smartPoleNoiseResponseDTO.ts | 2 +
.../model/smartPolePopulationResponseDTO.ts | 2 +
.../generated/model/smartPoleResponseDTO.ts | 1 +
.../safety-controller/safety-controller.ts | 75 ++++++++++++++++++-
16 files changed, 269 insertions(+), 9 deletions(-)
create mode 100644 shared/api/generated/model/environmentTotalDTO.ts
create mode 100644 shared/api/generated/model/getEnvironmentTotalParams.ts
create mode 100644 shared/api/generated/model/particulateMatter.ts
create mode 100644 shared/api/generated/model/policeModalResponse.ts
create mode 100644 shared/api/generated/model/propertyScore.ts
create mode 100644 shared/api/generated/model/responseDTOEnvironmentTotalDTO.ts
create mode 100644 shared/api/generated/model/responseDTOSafetyModalResponse.ts
create mode 100644 shared/api/generated/model/safetyModalResponse.ts
diff --git a/shared/api/generated/environment-controller/environment-controller.ts b/shared/api/generated/environment-controller/environment-controller.ts
index da57345..1fb1dcd 100644
--- a/shared/api/generated/environment-controller/environment-controller.ts
+++ b/shared/api/generated/environment-controller/environment-controller.ts
@@ -6,14 +6,21 @@
* OpenAPI spec version: v1.0.0
*/
import {
- useMutation
+ useMutation,
+ useQuery
} from '@tanstack/react-query'
import type {
MutationFunction,
+ QueryFunction,
+ QueryKey,
UseMutationOptions,
- UseMutationResult
+ UseMutationResult,
+ UseQueryOptions,
+ UseQueryResult
} from '@tanstack/react-query'
import type {
+ GetEnvironmentTotalParams,
+ ResponseDTOEnvironmentTotalDTO,
SaveWasteFacilityParams
} from '.././model'
import { customInstance } from '../../axios-instance';
@@ -73,4 +80,59 @@ const {mutation: mutationOptions, request: requestOptions} = options ?? {};
return useMutation(mutationOptions);
}
-
\ No newline at end of file
+ export const getEnvironmentTotal = (
+ params: GetEnvironmentTotalParams,
+ options?: SecondParameter,signal?: AbortSignal
+) => {
+
+
+ return customInstance(
+ {url: `/api/v1/environment/total`, method: 'GET',
+ params, signal
+ },
+ options);
+ }
+
+
+export const getGetEnvironmentTotalQueryKey = (params: GetEnvironmentTotalParams,) => {
+ return [`/api/v1/environment/total`, ...(params ? [params]: [])] as const;
+ }
+
+
+export const getGetEnvironmentTotalQueryOptions = >, TError = ErrorType>(params: GetEnvironmentTotalParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetEnvironmentTotalQueryKey(params);
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getEnvironmentTotal(params, requestOptions, signal);
+
+
+
+
+
+ return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetEnvironmentTotalQueryResult = NonNullable>>
+export type GetEnvironmentTotalQueryError = ErrorType
+
+export const useGetEnvironmentTotal = >, TError = ErrorType>(
+ params: GetEnvironmentTotalParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } => {
+
+ const queryOptions = getGetEnvironmentTotalQueryOptions(params,options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ query.queryKey = queryOptions.queryKey ;
+
+ return query;
+}
+
+
+
diff --git a/shared/api/generated/model/detailPropertyDTO.ts b/shared/api/generated/model/detailPropertyDTO.ts
index d41d27a..8d24ecb 100644
--- a/shared/api/generated/model/detailPropertyDTO.ts
+++ b/shared/api/generated/model/detailPropertyDTO.ts
@@ -10,6 +10,7 @@ import type { Facility } from './facility';
import type { PropertyImages } from './propertyImages';
import type { Option } from './option';
import type { PropertyInfo } from './propertyInfo';
+import type { PropertyScore } from './propertyScore';
import type { RealtorInfo } from './realtorInfo';
export interface DetailPropertyDTO {
@@ -19,5 +20,6 @@ export interface DetailPropertyDTO {
images?: PropertyImages;
option?: Option;
propertyInfo?: PropertyInfo;
+ propertyScore?: PropertyScore;
realtorInfo?: RealtorInfo;
}
diff --git a/shared/api/generated/model/environmentTotalDTO.ts b/shared/api/generated/model/environmentTotalDTO.ts
new file mode 100644
index 0000000..b3d3673
--- /dev/null
+++ b/shared/api/generated/model/environmentTotalDTO.ts
@@ -0,0 +1,17 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+import type { Item } from './item';
+import type { ParticulateMatter } from './particulateMatter';
+
+export interface EnvironmentTotalDTO {
+ enabled?: boolean;
+ environmentScore?: number;
+ items?: Item[];
+ particulateMatter?: ParticulateMatter;
+ radiusMeters?: number;
+}
diff --git a/shared/api/generated/model/getEnvironmentTotalParams.ts b/shared/api/generated/model/getEnvironmentTotalParams.ts
new file mode 100644
index 0000000..b487fe3
--- /dev/null
+++ b/shared/api/generated/model/getEnvironmentTotalParams.ts
@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+
+export type GetEnvironmentTotalParams = {
+propertyId: number;
+distance: number;
+};
diff --git a/shared/api/generated/model/index.ts b/shared/api/generated/model/index.ts
index ff3aa57..b6bbcb4 100644
--- a/shared/api/generated/model/index.ts
+++ b/shared/api/generated/model/index.ts
@@ -16,6 +16,7 @@ export * from './dealRequestDTOTradeType';
export * from './destinationDTO';
export * from './detailPropertyDTO';
export * from './districtRegisterDTO';
+export * from './environmentTotalDTO';
export * from './errorDTO';
export * from './facility';
export * from './facilityItem';
@@ -23,6 +24,7 @@ export * from './facilityItemInfraType';
export * from './facilityList';
export * from './facilityListFacilityType';
export * from './getConvenienceInfraParams';
+export * from './getEnvironmentTotalParams';
export * from './getMainPropertyParams';
export * from './getMainPropertyPropertyType';
export * from './getNearbyNoiseParams';
@@ -41,6 +43,8 @@ export * from './onboardRequestDTOPropertyType';
export * from './option';
export * from './optionList';
export * from './optionListOptionType';
+export * from './particulateMatter';
+export * from './policeModalResponse';
export * from './propertyCompareDTO';
export * from './propertyCompareDTOOrientation';
export * from './propertyImages';
@@ -56,6 +60,7 @@ export * from './propertyRegisterRequestDTOFacilitiesItem';
export * from './propertyRegisterRequestDTOOptionsItem';
export * from './propertyRegisterRequestDTOOrientation';
export * from './propertyRegisterRequestDTOPropertyType';
+export * from './propertyScore';
export * from './propertySearchRequestDTO';
export * from './propertySearchRequestDTODealType';
export * from './propertySearchRequestDTOPropertyType';
@@ -64,15 +69,18 @@ export * from './recentViewParams';
export * from './responseDTOCurationResponse';
export * from './responseDTODestinationDTO';
export * from './responseDTODetailPropertyDTO';
+export * from './responseDTOEnvironmentTotalDTO';
export * from './responseDTOListPropertyCompareDTO';
export * from './responseDTOListPropertyListDTO';
export * from './responseDTOMainPropertyResponseDTO';
export * from './responseDTONearbyInfraDTO';
export * from './responseDTONearbyNoiseDTO';
+export * from './responseDTOSafetyModalResponse';
export * from './responseDTOSmartPoleResponseDTO';
export * from './responseDTOTestResponseDTO';
export * from './responseDTOVoid';
export * from './responseDTOVoidData';
+export * from './safetyModalResponse';
export * from './saveCctvInfraParams';
export * from './saveConstructionInfraParams';
export * from './saveConvenienceStoreInfraParams';
diff --git a/shared/api/generated/model/particulateMatter.ts b/shared/api/generated/model/particulateMatter.ts
new file mode 100644
index 0000000..3dcf2bc
--- /dev/null
+++ b/shared/api/generated/model/particulateMatter.ts
@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+
+export interface ParticulateMatter {
+ pm10?: number;
+ pm25?: number;
+}
diff --git a/shared/api/generated/model/policeModalResponse.ts b/shared/api/generated/model/policeModalResponse.ts
new file mode 100644
index 0000000..b731346
--- /dev/null
+++ b/shared/api/generated/model/policeModalResponse.ts
@@ -0,0 +1,14 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+
+export interface PoliceModalResponse {
+ durationTime?: number;
+ latitude?: number;
+ longitude?: number;
+ policeOfficeName?: string;
+}
diff --git a/shared/api/generated/model/propertyListDTO.ts b/shared/api/generated/model/propertyListDTO.ts
index 8506bea..f43dae5 100644
--- a/shared/api/generated/model/propertyListDTO.ts
+++ b/shared/api/generated/model/propertyListDTO.ts
@@ -5,8 +5,8 @@
* Dingle API
* OpenAPI spec version: v1.0.0
*/
-import type { DealInfo } from "./dealInfo";
-import type { PropertyListDTOPropertyType } from "./propertyListDTOPropertyType";
+import type { DealInfo } from './dealInfo';
+import type { PropertyListDTOPropertyType } from './propertyListDTOPropertyType';
export interface PropertyListDTO {
apartmentName?: string;
@@ -18,5 +18,4 @@ export interface PropertyListDTO {
propertyType?: PropertyListDTOPropertyType;
supplyArea?: number;
totalFloor?: number;
- address?: string;
}
diff --git a/shared/api/generated/model/propertyScore.ts b/shared/api/generated/model/propertyScore.ts
new file mode 100644
index 0000000..6a78f7f
--- /dev/null
+++ b/shared/api/generated/model/propertyScore.ts
@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+
+export interface PropertyScore {
+ accessibilityScore?: number;
+ convenienceScore?: number;
+ environmentScore?: number;
+ noiseScore?: number;
+ safetyScore?: number;
+}
diff --git a/shared/api/generated/model/responseDTOEnvironmentTotalDTO.ts b/shared/api/generated/model/responseDTOEnvironmentTotalDTO.ts
new file mode 100644
index 0000000..573ff79
--- /dev/null
+++ b/shared/api/generated/model/responseDTOEnvironmentTotalDTO.ts
@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+import type { EnvironmentTotalDTO } from './environmentTotalDTO';
+import type { ErrorDTO } from './errorDTO';
+
+export interface ResponseDTOEnvironmentTotalDTO {
+ data?: EnvironmentTotalDTO;
+ error?: ErrorDTO;
+ success?: boolean;
+}
diff --git a/shared/api/generated/model/responseDTOSafetyModalResponse.ts b/shared/api/generated/model/responseDTOSafetyModalResponse.ts
new file mode 100644
index 0000000..1895d78
--- /dev/null
+++ b/shared/api/generated/model/responseDTOSafetyModalResponse.ts
@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+import type { SafetyModalResponse } from './safetyModalResponse';
+import type { ErrorDTO } from './errorDTO';
+
+export interface ResponseDTOSafetyModalResponse {
+ data?: SafetyModalResponse;
+ error?: ErrorDTO;
+ success?: boolean;
+}
diff --git a/shared/api/generated/model/safetyModalResponse.ts b/shared/api/generated/model/safetyModalResponse.ts
new file mode 100644
index 0000000..b79e737
--- /dev/null
+++ b/shared/api/generated/model/safetyModalResponse.ts
@@ -0,0 +1,15 @@
+/**
+ * Generated by orval v6.31.0 🍺
+ * Do not edit manually.
+ * Dingle Swagger API
+ * Dingle API
+ * OpenAPI spec version: v1.0.0
+ */
+import type { PoliceModalResponse } from './policeModalResponse';
+
+export interface SafetyModalResponse {
+ passedCrimeZone?: boolean;
+ pathCctvCount?: number;
+ pathLightCount?: number;
+ polices?: PoliceModalResponse[];
+}
diff --git a/shared/api/generated/model/smartPoleNoiseResponseDTO.ts b/shared/api/generated/model/smartPoleNoiseResponseDTO.ts
index b2272c3..e9a52e3 100644
--- a/shared/api/generated/model/smartPoleNoiseResponseDTO.ts
+++ b/shared/api/generated/model/smartPoleNoiseResponseDTO.ts
@@ -10,5 +10,7 @@ import type { Item } from './item';
export interface SmartPoleNoiseResponseDTO {
avgNoise?: number;
count?: number;
+ districtAvgNoise?: number;
items?: Item[];
+ overCount?: number;
}
diff --git a/shared/api/generated/model/smartPolePopulationResponseDTO.ts b/shared/api/generated/model/smartPolePopulationResponseDTO.ts
index 8dcaa37..abf1391 100644
--- a/shared/api/generated/model/smartPolePopulationResponseDTO.ts
+++ b/shared/api/generated/model/smartPolePopulationResponseDTO.ts
@@ -10,5 +10,7 @@ import type { Item } from './item';
export interface SmartPolePopulationResponseDTO {
avgPopulation?: number;
count?: number;
+ districtAvgPopulation?: number;
items?: Item[];
+ overCount?: number;
}
diff --git a/shared/api/generated/model/smartPoleResponseDTO.ts b/shared/api/generated/model/smartPoleResponseDTO.ts
index d885509..d932285 100644
--- a/shared/api/generated/model/smartPoleResponseDTO.ts
+++ b/shared/api/generated/model/smartPoleResponseDTO.ts
@@ -9,6 +9,7 @@ import type { SmartPoleNoiseResponseDTO } from './smartPoleNoiseResponseDTO';
import type { SmartPolePopulationResponseDTO } from './smartPolePopulationResponseDTO';
export interface SmartPoleResponseDTO {
+ districtId?: number;
noise?: SmartPoleNoiseResponseDTO;
population?: SmartPolePopulationResponseDTO;
propertyId?: number;
diff --git a/shared/api/generated/safety-controller/safety-controller.ts b/shared/api/generated/safety-controller/safety-controller.ts
index 8b74001..2906ead 100644
--- a/shared/api/generated/safety-controller/safety-controller.ts
+++ b/shared/api/generated/safety-controller/safety-controller.ts
@@ -6,13 +6,21 @@
* OpenAPI spec version: v1.0.0
*/
import {
- useMutation
+ useMutation,
+ useQuery
} from '@tanstack/react-query'
import type {
MutationFunction,
+ QueryFunction,
+ QueryKey,
UseMutationOptions,
- UseMutationResult
+ UseMutationResult,
+ UseQueryOptions,
+ UseQueryResult
} from '@tanstack/react-query'
+import type {
+ ResponseDTOSafetyModalResponse
+} from '.././model'
import { customInstance } from '../../axios-instance';
import type { ErrorType } from '../../axios-instance';
@@ -69,4 +77,65 @@ const {mutation: mutationOptions, request: requestOptions} = options ?? {};
return useMutation(mutationOptions);
}
-
\ No newline at end of file
+ /**
+ * 안전 모달을 조회합니다.
+ * @summary 지도 안전 모달 조회 API (이거 아직 완성 아님)
+ */
+export const getSafetyModal = (
+ propertyId: number,
+ options?: SecondParameter,signal?: AbortSignal
+) => {
+
+
+ return customInstance(
+ {url: `/api/v1/safety/${propertyId}/modal`, method: 'GET', signal
+ },
+ options);
+ }
+
+
+export const getGetSafetyModalQueryKey = (propertyId: number,) => {
+ return [`/api/v1/safety/${propertyId}/modal`] as const;
+ }
+
+
+export const getGetSafetyModalQueryOptions = >, TError = ErrorType>(propertyId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetSafetyModalQueryKey(propertyId);
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getSafetyModal(propertyId, requestOptions, signal);
+
+
+
+
+
+ return { queryKey, queryFn, enabled: !!(propertyId), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetSafetyModalQueryResult = NonNullable>>
+export type GetSafetyModalQueryError = ErrorType
+
+/**
+ * @summary 지도 안전 모달 조회 API (이거 아직 완성 아님)
+ */
+export const useGetSafetyModal = >, TError = ErrorType>(
+ propertyId: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } => {
+
+ const queryOptions = getGetSafetyModalQueryOptions(propertyId,options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ query.queryKey = queryOptions.queryKey ;
+
+ return query;
+}
+
+
+
From e3a4ac0771dc9f99fbbbeaed727a531af5d7d550 Mon Sep 17 00:00:00 2001
From: HSCHEOL
Date: Mon, 9 Feb 2026 19:04:17 +0900
Subject: [PATCH 3/9] =?UTF-8?q?feat:=EC=A2=85=ED=95=A9=20=EB=8D=B0?=
=?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=AA=A8=EB=93=9C=20=ED=8E=B8=EC=9D=98=20?=
=?UTF-8?q?=EC=A2=85=ED=95=A9=20=EC=A0=90=EC=88=98=20API=20=EC=97=B0?=
=?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20=EB=A7=88=EC=BB=A4=20=ED=91=9C=EC=8B=9C?=
=?UTF-8?q?/=EB=A7=88=EC=BB=A4=20=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/list/List.tsx | 37 ++-
app/components/list/ListDetail.tsx | 30 ++-
app/components/map/KakaoMap.tsx | 233 +++++++++++++++++-
app/components/map/MapHeader.tsx | 5 +-
app/components/module/AccessibilityModule.tsx | 5 +-
app/components/module/ConvenienceModule.tsx | 62 ++++-
app/components/module/EnvironmentModule.tsx | 5 +-
app/components/module/NoiseModule.tsx | 5 +-
app/components/module/SafetyModule.tsx | 5 +-
app/map/page.tsx | 75 +++---
app/store/mapModeStore.ts | 34 ++-
app/store/moduleStore.ts | 3 +
public/map_marker/clicked_marker.svg | 21 ++
.../convenience/hospital_marker.svg | 24 ++
public/map_marker/convenience/mart_marker.svg | 25 ++
.../map_marker/convenience/store_marker.svg | 24 ++
public/map_marker/map_marker.svg | 52 ++++
public/map_marker/normal_marker.svg | 21 ++
18 files changed, 610 insertions(+), 56 deletions(-)
create mode 100644 public/map_marker/clicked_marker.svg
create mode 100644 public/map_marker/convenience/hospital_marker.svg
create mode 100644 public/map_marker/convenience/mart_marker.svg
create mode 100644 public/map_marker/convenience/store_marker.svg
create mode 100644 public/map_marker/map_marker.svg
create mode 100644 public/map_marker/normal_marker.svg
diff --git a/app/components/list/List.tsx b/app/components/list/List.tsx
index a3fa047..0988c25 100644
--- a/app/components/list/List.tsx
+++ b/app/components/list/List.tsx
@@ -8,6 +8,7 @@ import ListDetail from "./ListDetail";
import PriorityToggle from "../ui/PriorityToggle";
import { useUserStore } from "@/app/store/userStore";
import { useModuleStore, ModuleId } from "@/app/store/moduleStore";
+import { useMapModeStore } from "@/app/store/mapModeStore";
import { useGetMainProperty } from "@/shared/api/generated/main-property-controller/main-property-controller";
import { DealInfoDealType } from "@/shared/api/generated/model/dealInfoDealType";
import { formatNumberToKoreanPrice } from "@/app/utils/format";
@@ -40,6 +41,7 @@ const List = () => {
const { toggleZzim } = usePropertyZzim();
const { addViewedId } = useRecentViewStore();
const { selectedPropertyType } = usePropertyStore();
+ const { setPropertiesOnMap } = useMapModeStore();
useEffect(() => {
const timer = setTimeout(() => {
@@ -64,7 +66,7 @@ const List = () => {
{
select: user?.preferredConditions || [],
propertyType: selectedPropertyType,
- size: 10,
+ size: 30,
},
{
query: {
@@ -73,6 +75,39 @@ const List = () => {
},
);
+ // 현재 리스트의 매물들을 지도에 표시하기 위해 동기화
+ useEffect(() => {
+ if (apiResponse?.data?.items) {
+ const markers = apiResponse.data.items
+ .filter((item) => item.latitude && item.longitude)
+ .map((item) => {
+ let priceStr = "";
+ let dealLabel = "";
+ const deal = item.dealInfo;
+ if (deal?.dealType === DealInfoDealType.RENT) {
+ priceStr = `${deal.deposit}/${deal.monthlyRent}`;
+ dealLabel = "월세";
+ } else if (deal?.dealType === DealInfoDealType.LEASE) {
+ priceStr = formatNumberToKoreanPrice(deal.price || 0);
+ dealLabel = "전세";
+ } else if (deal?.dealType === DealInfoDealType.SALE) {
+ priceStr = formatNumberToKoreanPrice(deal.price || 0);
+ dealLabel = "매매";
+ }
+
+ return {
+ id: item.propertyId || 0,
+ lat: item.latitude!,
+ lng: item.longitude!,
+ title: item.apartmentName || "",
+ price: priceStr,
+ dealType: dealLabel,
+ };
+ });
+ setPropertiesOnMap(markers);
+ }
+ }, [apiResponse, setPropertiesOnMap]);
+
const houses =
apiResponse?.data?.items?.map((item, index) => {
let priceStr = "";
diff --git a/app/components/list/ListDetail.tsx b/app/components/list/ListDetail.tsx
index d7439bb..ba11856 100644
--- a/app/components/list/ListDetail.tsx
+++ b/app/components/list/ListDetail.tsx
@@ -20,6 +20,7 @@ import { useCurate } from "@/shared/api/generated/personalized-curation-controll
import { useMapModeStore } from "@/app/store/mapModeStore";
import { usePropertyZzim } from "@/app/hooks/usePropertyZzim";
+import { formatNumberToKoreanPrice } from "@/app/utils/format";
const sections = [
{ id: "curation", label: "맞춤 큐레이션 정보" },
@@ -43,7 +44,8 @@ const ListDetail = ({
null,
);
const [isContactModalOpen, setIsContactModalOpen] = useState(false);
- const { setSelectedProperty, clearSelectedProperty } = useMapModeStore();
+ const { isMapMode, setSelectedProperty, clearSelectedProperty } =
+ useMapModeStore();
const { toggleZzim } = usePropertyZzim();
const isManualScrolling = useRef(false);
@@ -58,17 +60,41 @@ const ListDetail = ({
useEffect(() => {
if (detailData?.propertyInfo) {
const { latitude, longitude, apartmentName } = detailData.propertyInfo;
+ const deal = detailData.deal;
+ let priceStr = "";
+ let dealLabel = "";
+
+ if (deal) {
+ if (deal.dealType === "RENT") {
+ priceStr = `${deal.deposit}/${deal.monthlyRent}`;
+ dealLabel = "월세";
+ } else if (deal.dealType === "LEASE") {
+ priceStr = formatNumberToKoreanPrice(deal.price || 0);
+ dealLabel = "전세";
+ } else if (deal.dealType === "SALE") {
+ priceStr = formatNumberToKoreanPrice(deal.price || 0);
+ dealLabel = "매매";
+ }
+ }
+
if (latitude && longitude) {
setSelectedProperty({
+ id: propertyId,
lat: latitude,
lng: longitude,
title: apartmentName || "매물 위치",
+ price: priceStr,
+ dealType: dealLabel,
+ propertyScores: detailData.propertyScore,
});
}
}
return () => {
- clearSelectedProperty();
+ // 렌더링 사이클에 의존하지 않고 전역 스토어의 최신 상태를 직접 확인
+ if (!useMapModeStore.getState().isMapMode) {
+ clearSelectedProperty();
+ }
};
}, [detailData, setSelectedProperty, clearSelectedProperty]);
diff --git a/app/components/map/KakaoMap.tsx b/app/components/map/KakaoMap.tsx
index 5b77f84..fd2e7a2 100644
--- a/app/components/map/KakaoMap.tsx
+++ b/app/components/map/KakaoMap.tsx
@@ -1,13 +1,121 @@
"use client";
import React, { useEffect, useState } from "react";
-import { Map, MapMarker, useKakaoLoader } from "react-kakao-maps-sdk";
+import {
+ Map,
+ MapMarker,
+ CustomOverlayMap,
+ useKakaoLoader,
+} from "react-kakao-maps-sdk";
import { useMapModeStore } from "../../store/mapModeStore";
+import { cn } from "@/app/lib/utils";
+import { FacilityItemInfraType } from "@/shared/api/generated/model/facilityItemInfraType";
+import { FacilityItem } from "@/shared/api/generated/model/facilityItem";
+
+const CustomMarker = ({
+ lat,
+ lng,
+ dealType,
+ price,
+ isSelected,
+}: {
+ lat: number;
+ lng: number;
+ dealType?: string;
+ price?: string;
+ isSelected: boolean;
+}) => {
+ const size = isSelected ? 80 : 70;
+ const iconSrc = isSelected
+ ? "/map_marker/clicked_marker.svg"
+ : "/map_marker/normal_marker.svg";
+
+ return (
+
+
+
+
+ {dealType}
+
+
+ {price}
+
+
+
+
+ );
+};
+
+const FacilityOverlay = ({
+ infra,
+ onClose,
+}: {
+ infra: FacilityItem & { address?: string };
+ onClose: () => void;
+}) => {
+ return (
+ e.stopPropagation()}
+ >
+
+
+ {infra.name}
+
+
+
+
+
+ {infra.address || "주소 정보가 없습니다."}
+
+
+
+ );
+};
const KakaoMap = () => {
- const { selectedProperty } = useMapModeStore();
+ const {
+ isMapMode,
+ selectedProperty,
+ propertiesOnMap,
+ convenienceInfras,
+ selectedInfra,
+ setSelectedInfra,
+ clearSelectedInfra,
+ } = useMapModeStore();
const [map, setMap] = useState(null);
+ // 편의 시설 마커 아이콘 매핑
+ const infraIconMap: Record = {
+ [FacilityItemInfraType.HOSPITAL]:
+ "/map_marker/convenience/hospital_marker.svg",
+ [FacilityItemInfraType.EMERGENCY_MEDICAL_CENTER]:
+ "/map_marker/convenience/hospital_marker.svg",
+ [FacilityItemInfraType.MARKET]: "/map_marker/convenience/mart_marker.svg",
+ [FacilityItemInfraType.CONVENIENCE_STORE]:
+ "/map_marker/convenience/store_marker.svg",
+ };
+
// 기본 중심값: 약수역
const defaultCenter = { lat: 37.5545, lng: 127.0112 };
@@ -20,7 +128,7 @@ const KakaoMap = () => {
appkey: process.env.NEXT_PUBLIC_KAKAO_MAP_API_KEY || "",
});
- // 매물이 선택되었을 때 오프셋 적용 (왼쪽 800px 패널 고려)
+ // 매물이 선택되었을 때 오프셋 적용
useEffect(() => {
if (map && selectedProperty) {
// 1. 먼저 해당 좌표로 중심 이동
@@ -30,11 +138,13 @@ const KakaoMap = () => {
);
map.setCenter(moveLatLng);
- // 2. 우측 가시 영역 중앙으로 오게 하기 위해 왼쪽(이미지상 좌측이동 = 지도뷰 우측이동)으로 -400px 패닝
- // Sidebar(800px) / 2 = 400px 만큼 우측으로 밀어줌
- map.panBy(-400, 0);
+ // 2. 일반 모드일 때만 우측 가시 영역 중앙으로 오게 하기 위해 왼쪽으로 -400px 패닝
+ // 종합데이터 모드(isMapMode = true)에서는 사이드바가 없으므로 정중앙 유지
+ if (!isMapMode) {
+ map.panBy(-400, 0);
+ }
}
- }, [map, selectedProperty]);
+ }, [map, selectedProperty, isMapMode]);
if (loading)
return ;
@@ -51,12 +161,111 @@ const KakaoMap = () => {
style={{ width: "100%", height: "100%" }}
level={selectedProperty ? 2 : 3}
onCreate={setMap}
+ onClick={() => {
+ clearSelectedInfra();
+ }}
>
- {selectedProperty && (
-
+ {isMapMode ? (
+ <>
+ {/* 종합데이터 모드: 편의 시설 마커 */}
+ {convenienceInfras.map((infra) => {
+ const iconSrc = infra.infraType
+ ? infraIconMap[infra.infraType]
+ : null;
+ if (!iconSrc || !infra.latitude || !infra.longitude) return null;
+
+ return (
+
+ setSelectedInfra(infra)}
+ />
+
+ );
+ })}
+
+ {/* 편의 시설 커스텀 오버레이 */}
+ {selectedInfra &&
+ selectedInfra.latitude &&
+ selectedInfra.longitude && (
+
+ clearSelectedInfra()}
+ />
+
+ )}
+
+ {/* 종합데이터 모드: 선택된 매물만 map_marker.svg로 표시 */}
+ {selectedProperty && (
+
+ )}
+ >
+ ) : (
+ <>
+ {/* 일반 모드: 리스트상의 모든 매물 렌더링 */}
+ {propertiesOnMap.map((prop) => {
+ const isSelected =
+ selectedProperty?.lat === prop.lat &&
+ selectedProperty?.lng === prop.lng;
+ return (
+
+ );
+ })}
+
+ {/* 선택된 매물이 리스트에 없을 수도 있으므로 별도 렌더링 */}
+ {selectedProperty &&
+ !propertiesOnMap.find(
+ (p) =>
+ p.lat === selectedProperty.lat &&
+ p.lng === selectedProperty.lng,
+ ) && (
+
+ )}
+ >
)}
);
diff --git a/app/components/map/MapHeader.tsx b/app/components/map/MapHeader.tsx
index 21869bd..d4aff41 100644
--- a/app/components/map/MapHeader.tsx
+++ b/app/components/map/MapHeader.tsx
@@ -8,8 +8,9 @@ interface MapHeaderProps {
title?: string;
}
-const MapHeader = ({ title = "약수하이츠아파트" }: MapHeaderProps) => {
- const { setMapMode } = useMapModeStore();
+const MapHeader = () => {
+ const { setMapMode, selectedProperty } = useMapModeStore();
+ const title = selectedProperty?.title || "매물 위치";
return (
diff --git a/app/components/module/AccessibilityModule.tsx b/app/components/module/AccessibilityModule.tsx
index 363913c..6e3623b 100644
--- a/app/components/module/AccessibilityModule.tsx
+++ b/app/components/module/AccessibilityModule.tsx
@@ -1,6 +1,7 @@
import React from "react";
import Image from "next/image";
import { useModuleStore } from "@/app/store/moduleStore";
+import { useMapModeStore } from "@/app/store/mapModeStore";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@@ -10,7 +11,9 @@ function cn(...inputs: ClassValue[]) {
const AccessibilityModule = () => {
const { activeModules, toggleModule } = useModuleStore();
+ const { selectedProperty } = useMapModeStore();
const isActive = activeModules.includes("accessibility");
+ const score = selectedProperty?.propertyScores?.accessibilityScore || 0;
return (
{
isActive ? "text-[#0192C8]" : "text-[#555555]",
)}
>
- 82점
+ {score}점
diff --git a/app/components/module/ConvenienceModule.tsx b/app/components/module/ConvenienceModule.tsx
index b4c820b..b5b122b 100644
--- a/app/components/module/ConvenienceModule.tsx
+++ b/app/components/module/ConvenienceModule.tsx
@@ -2,6 +2,9 @@ import React, { useState } from "react";
import Image from "next/image";
import { Check } from "lucide-react";
import { useModuleStore } from "@/app/store/moduleStore";
+import { useMapModeStore } from "@/app/store/mapModeStore";
+import { useGetConvenienceInfra } from "@/shared/api/generated/infra-controller/infra-controller";
+import { useEffect } from "react";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@@ -11,16 +14,69 @@ function cn(...inputs: ClassValue[]) {
const ConvenienceModule = () => {
const { activeModules, toggleModule } = useModuleStore();
+ const {
+ selectedProperty,
+ setConvenienceInfras,
+ clearConvenienceInfras,
+ clearSelectedInfra,
+ } = useMapModeStore();
const isActive = activeModules.includes("convenience");
const [selectedTime, setSelectedTime] = useState(5); // 5, 10, 20
+ const score = selectedProperty?.propertyScores?.convenienceScore || 0;
// Checkbox states for the facilities
const [checkedItems, setCheckedItems] = useState({
- store: false,
+ store: true, // 기본값 true로 설정 (사용자 요청에 따라 마커가 찍혀야 하므로)
market: true,
- hospital: false,
+ hospital: true,
});
+ // 편의 정보 데이터 호출
+ const infraTypes: string[] = [];
+ if (checkedItems.store) infraTypes.push("CONVENIENCE_STORE");
+ if (checkedItems.market) infraTypes.push("MARKET");
+ if (checkedItems.hospital) infraTypes.push("HOSPITAL");
+
+ const infraTypesJoined = infraTypes.length > 0 ? infraTypes.join(", ") : "";
+
+ const { data: infraData } = useGetConvenienceInfra(
+ {
+ request: {
+ propertyId: selectedProperty?.id,
+ walkMinutes: selectedTime,
+ infraTypes: (infraTypesJoined as any) || undefined,
+ },
+ } as any,
+ {
+ query: {
+ enabled: isActive && !!selectedProperty?.id,
+ },
+ request: {
+ params: {
+ propertyId: selectedProperty?.id,
+ walkMinutes: selectedTime,
+ infraTypes: infraTypesJoined || undefined,
+ },
+ },
+ },
+ );
+
+ // 데이터가 오면 스토어에 동기화
+ useEffect(() => {
+ if (isActive && infraData?.data?.facilities) {
+ setConvenienceInfras(infraData.data.facilities);
+ } else if (!isActive) {
+ clearConvenienceInfras();
+ clearSelectedInfra();
+ }
+ }, [
+ isActive,
+ infraData,
+ setConvenienceInfras,
+ clearConvenienceInfras,
+ clearSelectedInfra,
+ ]);
+
const toggleItem = (key: keyof typeof checkedItems) => {
setCheckedItems((prev) => ({ ...prev, [key]: !prev[key] }));
};
@@ -65,7 +121,7 @@ const ConvenienceModule = () => {
isActive ? "text-[#745BCD]" : "text-[#555555]",
)}
>
- 82점
+ {score}점
diff --git a/app/components/module/EnvironmentModule.tsx b/app/components/module/EnvironmentModule.tsx
index 46969ff..8ed1ce1 100644
--- a/app/components/module/EnvironmentModule.tsx
+++ b/app/components/module/EnvironmentModule.tsx
@@ -1,6 +1,7 @@
import React from "react";
import Image from "next/image";
import { useModuleStore } from "@/app/store/moduleStore";
+import { useMapModeStore } from "@/app/store/mapModeStore";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@@ -10,7 +11,9 @@ function cn(...inputs: ClassValue[]) {
const EnvironmentModule = () => {
const { activeModules, toggleModule } = useModuleStore();
+ const { selectedProperty } = useMapModeStore();
const isActive = activeModules.includes("environment");
+ const score = selectedProperty?.propertyScores?.environmentScore || 0;
return (
{
isActive ? "text-[#29AD29]" : "text-[#555555]",
)}
>
- 82점
+ {score}점
diff --git a/app/components/module/NoiseModule.tsx b/app/components/module/NoiseModule.tsx
index aaed938..c7a01c0 100644
--- a/app/components/module/NoiseModule.tsx
+++ b/app/components/module/NoiseModule.tsx
@@ -1,6 +1,7 @@
import React from "react";
import Image from "next/image";
import { useModuleStore } from "@/app/store/moduleStore";
+import { useMapModeStore } from "@/app/store/mapModeStore";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@@ -10,7 +11,9 @@ function cn(...inputs: ClassValue[]) {
const NoiseModule = () => {
const { activeModules, toggleModule } = useModuleStore();
+ const { selectedProperty } = useMapModeStore();
const isActive = activeModules.includes("noise");
+ const score = selectedProperty?.propertyScores?.noiseScore || 0;
return (
{
isActive ? "text-[#EA8B2B]" : "text-[#555555]",
)}
>
- 82점
+ {score}점
diff --git a/app/components/module/SafetyModule.tsx b/app/components/module/SafetyModule.tsx
index f31292b..9e8cf0d 100644
--- a/app/components/module/SafetyModule.tsx
+++ b/app/components/module/SafetyModule.tsx
@@ -3,6 +3,7 @@
import React, { useState } from "react";
import Image from "next/image";
import { useModuleStore } from "@/app/store/moduleStore";
+import { useMapModeStore } from "@/app/store/mapModeStore";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@@ -12,8 +13,10 @@ function cn(...inputs: ClassValue[]) {
const SafetyModule = () => {
const { activeModules, toggleModule } = useModuleStore();
+ const { selectedProperty } = useMapModeStore();
const isActive = activeModules.includes("safety");
const [isMainRoadMode, setIsMainRoadMode] = useState(false);
+ const score = selectedProperty?.propertyScores?.safetyScore || 0;
return (
{
isActive ? "text-[#E05353]" : "text-[#555555]",
)}
>
- 82점
+ {score}점
diff --git a/app/map/page.tsx b/app/map/page.tsx
index 5928643..409fbf7 100644
--- a/app/map/page.tsx
+++ b/app/map/page.tsx
@@ -20,43 +20,58 @@ import { Toast } from "../components/ui/Toast";
const MapPage = () => {
const { isMapMode } = useMapModeStore();
const { user } = useUserStore();
- const {
- activeModules,
- toggleModule,
- toastMessage,
- setToastMessage,
- getDisplayOrder,
- } = useModuleStore();
+ const { resetModules, toastMessage, setToastMessage, getDisplayOrder } =
+ useModuleStore();
const initialActivatedRef = useRef(false);
+ const listInitialActivatedRef = useRef(false);
- // 초기 진입 시 온보딩 우선순위 활성화 로직
+ // 초기 진입 및 모드 전환 시 우선순위 활성화 로직
useEffect(() => {
- if (
- user?.preferredConditions &&
- user.preferredConditions.length > 0 &&
- !initialActivatedRef.current &&
- activeModules.length === 0
- ) {
- const idMap: Record = {
- 1: "noise",
- 2: "environment",
- 3: "safety",
- 4: "accessibility",
- 5: "convenience",
- };
-
- // 온보딩에서 선택한 우선순위들을 활성화 (최대 3개 권장이나 온보딩 데이터 전체 반영)
- user.preferredConditions.forEach((priorityId) => {
- const moduleId = idMap[priorityId];
- if (moduleId && !activeModules.includes(moduleId)) {
- toggleModule(moduleId);
+ if (!user?.preferredConditions || user.preferredConditions.length === 0)
+ return;
+
+ const idMap: Record = {
+ 1: "noise",
+ 2: "environment",
+ 3: "safety",
+ 4: "accessibility",
+ 5: "convenience",
+ };
+
+ if (isMapMode) {
+ // 종합데이터 모드 진입 시: 첫 번째 우선순위 모듈만 활성화
+ if (!initialActivatedRef.current) {
+ const firstPriorityId = user.preferredConditions[0];
+ const moduleId = idMap[firstPriorityId];
+
+ if (moduleId) {
+ resetModules([moduleId]);
}
- });
+ initialActivatedRef.current = true;
+ }
+
+ // 리스트 뷰 초기화 플래그 리셋 (다시 돌아갈 때 다시 켜지도록)
+ if (listInitialActivatedRef.current) {
+ listInitialActivatedRef.current = false;
+ }
+ } else {
+ // 리스트 뷰 (초기 혹은 복귀): 온보딩 모든 우선순위 활성화
+ if (!listInitialActivatedRef.current) {
+ const preferredModuleIds = user.preferredConditions
+ .map((id) => idMap[id])
+ .filter(Boolean) as ModuleId[];
+
+ resetModules(preferredModuleIds);
+ listInitialActivatedRef.current = true;
+ }
- initialActivatedRef.current = true;
+ // 종합데이터 모드 초기화 플래그 리셋
+ if (initialActivatedRef.current) {
+ initialActivatedRef.current = false;
+ }
}
- }, [user, activeModules, toggleModule]);
+ }, [isMapMode, user, resetModules]);
const renderModule = (id: ModuleId) => {
switch (id) {
diff --git a/app/store/mapModeStore.ts b/app/store/mapModeStore.ts
index cbc551d..6d28626 100644
--- a/app/store/mapModeStore.ts
+++ b/app/store/mapModeStore.ts
@@ -1,9 +1,15 @@
import { create } from "zustand";
+import { PropertyScore } from "@/shared/api/generated/model/propertyScore";
+import { FacilityItem } from "@/shared/api/generated/model/facilityItem";
interface SelectedProperty {
+ id: number;
lat: number;
lng: number;
title: string;
+ price?: string;
+ dealType?: string;
+ propertyScores?: PropertyScore;
}
interface MapModeState {
@@ -12,12 +18,36 @@ interface MapModeState {
selectedProperty: SelectedProperty | null;
setSelectedProperty: (property: SelectedProperty) => void;
clearSelectedProperty: () => void;
+ propertiesOnMap: (SelectedProperty & { id: number })[];
+ setPropertiesOnMap: (
+ properties: (SelectedProperty & { id: number })[],
+ ) => void;
+ clearPropertiesOnMap: () => void;
+ convenienceInfras: FacilityItem[];
+ setConvenienceInfras: (infras: FacilityItem[]) => void;
+ clearConvenienceInfras: () => void;
+ selectedInfra: FacilityItem | null;
+ setSelectedInfra: (infra: FacilityItem | null) => void;
+ clearSelectedInfra: () => void;
}
export const useMapModeStore = create((set) => ({
isMapMode: false,
- setMapMode: (isMapMode) => set({ isMapMode }),
+ setMapMode: (isMapMode: boolean) => set({ isMapMode }),
selectedProperty: null,
- setSelectedProperty: (selectedProperty) => set({ selectedProperty }),
+ setSelectedProperty: (selectedProperty: SelectedProperty) =>
+ set({ selectedProperty }),
clearSelectedProperty: () => set({ selectedProperty: null }),
+ propertiesOnMap: [],
+ setPropertiesOnMap: (properties: (SelectedProperty & { id: number })[]) =>
+ set({ propertiesOnMap: properties }),
+ clearPropertiesOnMap: () => set({ propertiesOnMap: [] }),
+ convenienceInfras: [],
+ setConvenienceInfras: (infras: FacilityItem[]) =>
+ set({ convenienceInfras: infras }),
+ clearConvenienceInfras: () => set({ convenienceInfras: [] }),
+ selectedInfra: null,
+ setSelectedInfra: (infra: FacilityItem | null) =>
+ set({ selectedInfra: infra }),
+ clearSelectedInfra: () => set({ selectedInfra: null }),
}));
diff --git a/app/store/moduleStore.ts b/app/store/moduleStore.ts
index 9bccddf..7111ffe 100644
--- a/app/store/moduleStore.ts
+++ b/app/store/moduleStore.ts
@@ -27,6 +27,7 @@ interface ModuleState {
toastMessage: string | null;
toggleModule: (id: ModuleId) => void;
+ resetModules: (ids?: ModuleId[]) => void;
setToastMessage: (message: string | null) => void;
getDisplayOrder: () => ModuleId[]; // 렌더링 순서 계산 (활성 모듈 우선 + 미활성 모듈)
}
@@ -60,6 +61,8 @@ export const useModuleStore = create((set, get) => ({
}
},
+ resetModules: (ids = []) => set({ activeModules: ids }),
+
setToastMessage: (message) => set({ toastMessage: message }),
getDisplayOrder: () => {
diff --git a/public/map_marker/clicked_marker.svg b/public/map_marker/clicked_marker.svg
new file mode 100644
index 0000000..ca0cd86
--- /dev/null
+++ b/public/map_marker/clicked_marker.svg
@@ -0,0 +1,21 @@
+
diff --git a/public/map_marker/convenience/hospital_marker.svg b/public/map_marker/convenience/hospital_marker.svg
new file mode 100644
index 0000000..2ac5925
--- /dev/null
+++ b/public/map_marker/convenience/hospital_marker.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/map_marker/convenience/mart_marker.svg b/public/map_marker/convenience/mart_marker.svg
new file mode 100644
index 0000000..139b932
--- /dev/null
+++ b/public/map_marker/convenience/mart_marker.svg
@@ -0,0 +1,25 @@
+
diff --git a/public/map_marker/convenience/store_marker.svg b/public/map_marker/convenience/store_marker.svg
new file mode 100644
index 0000000..929edf2
--- /dev/null
+++ b/public/map_marker/convenience/store_marker.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/map_marker/map_marker.svg b/public/map_marker/map_marker.svg
new file mode 100644
index 0000000..d70b905
--- /dev/null
+++ b/public/map_marker/map_marker.svg
@@ -0,0 +1,52 @@
+
diff --git a/public/map_marker/normal_marker.svg b/public/map_marker/normal_marker.svg
new file mode 100644
index 0000000..167c230
--- /dev/null
+++ b/public/map_marker/normal_marker.svg
@@ -0,0 +1,21 @@
+
From a26c01397535ac83dccb5e3ff8577c53602caad9 Mon Sep 17 00:00:00 2001
From: HSCHEOL
Date: Mon, 9 Feb 2026 20:01:36 +0900
Subject: [PATCH 4/9] =?UTF-8?q?feat:=EC=A2=85=ED=95=A9=20=EB=8D=B0?=
=?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=AA=A8=EB=93=9C=20=ED=99=98=EA=B2=BD=20?=
=?UTF-8?q?=EC=A2=85=ED=95=A9=20=EC=A0=90=EC=88=98=20API=20=EC=97=B0?=
=?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20=EB=A7=88=EC=BB=A4=20=ED=91=9C=EC=8B=9C?=
=?UTF-8?q?/=EB=A7=88=EC=BB=A4=20=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../list/list_section/CurationSection.tsx | 65 +++++++++----
app/components/map/KakaoMap.tsx | 94 +++++++++++++++++++
app/components/module/EnvironmentModule.tsx | 76 +++++++++++++--
app/store/mapModeStore.ts | 14 +++
.../map_marker/environment/funeral_marker.svg | 24 +++++
.../environment/mountain_marker.svg | 24 +++++
public/map_marker/environment/park_marker.svg | 24 +++++
.../map_marker/environment/waste_marker.svg | 24 +++++
shared/api/generated/model/item.ts | 4 +-
9 files changed, 323 insertions(+), 26 deletions(-)
create mode 100644 public/map_marker/environment/funeral_marker.svg
create mode 100644 public/map_marker/environment/mountain_marker.svg
create mode 100644 public/map_marker/environment/park_marker.svg
create mode 100644 public/map_marker/environment/waste_marker.svg
diff --git a/app/components/list/list_section/CurationSection.tsx b/app/components/list/list_section/CurationSection.tsx
index caced3e..768f0a7 100644
--- a/app/components/list/list_section/CurationSection.tsx
+++ b/app/components/list/list_section/CurationSection.tsx
@@ -35,12 +35,48 @@ const CurationSection = ({
isAiLoading,
}: CurationSectionProps) => {
const [hoveredCategory, setHoveredCategory] = useState(null);
- const { setMapMode } = useMapModeStore();
+ const { setMapMode, selectedProperty } = useMapModeStore();
const sortedMatchedConditions = (conditions || [])
.map((cId) => CONDITION_MAP[cId])
.filter(Boolean);
+ // 실제 점수 데이터 가져오기 (기본값 0)
+ const scores = {
+ 안전: selectedProperty?.propertyScores?.safetyScore ?? 0,
+ 환경: selectedProperty?.propertyScores?.environmentScore ?? 0,
+ 접근성: selectedProperty?.propertyScores?.accessibilityScore ?? 0,
+ 편의: selectedProperty?.propertyScores?.convenienceScore ?? 0,
+ 소음: selectedProperty?.propertyScores?.noiseScore ?? 0,
+ };
+
+ // 레이더 차트 좌표 계산 (중심: 80, 80 / 최대 반지름: 80)
+ const getPoint = (score: number, angle: number) => {
+ const radius = 80 * (score / 100);
+ const rad = ((angle - 90) * Math.PI) / 180;
+ return {
+ x: 80 + radius * Math.cos(rad),
+ y: 80 + radius * Math.sin(rad),
+ };
+ };
+
+ // 5가지 요소의 각도 (안전, 환경, 접근성, 편의, 소음)
+ const angles = [0, 72, 144, 216, 288];
+ const radarScores = [
+ scores.안전,
+ scores.환경,
+ scores.접근성,
+ scores.편의,
+ scores.소음,
+ ];
+
+ const points = angles
+ .map((angle, i) => {
+ const p = getPoint(radarScores[i], angle);
+ return `${p.x},${p.y}`;
+ })
+ .join(" ");
+
return (
@@ -151,14 +187,11 @@ const CurationSection = ({
-
+
- {/* Labels & Scores with Tooltips (Mock values as agreed) */}
- {/* 1. 안전 (Top - 83) */}
+ {/* Labels & Scores with Tooltips */}
+ {/* 1. 안전 */}
setHoveredCategory("안전")}
@@ -182,7 +215,7 @@ const CurationSection = ({
- 83
+ {scores.안전}
{hoveredCategory === "안전" && (
@@ -193,7 +226,7 @@ const CurationSection = ({
)}
- {/* 2. 환경 (Right-ish - 67) */}
+ {/* 2. 환경 */}
setHoveredCategory("환경")}
@@ -217,7 +250,7 @@ const CurationSection = ({
- 67
+ {scores.환경}
{hoveredCategory === "환경" && (
@@ -228,7 +261,7 @@ const CurationSection = ({
)}
- {/* 3. 접근성 (Bottom-Right - 45) */}
+ {/* 3. 접근성 */}
setHoveredCategory("접근성")}
@@ -252,7 +285,7 @@ const CurationSection = ({
- 45
+ {scores.접근성}
{hoveredCategory === "접근성" && (
@@ -263,7 +296,7 @@ const CurationSection = ({
)}
- {/* 4. 편의 (Bottom-Left - 60) */}
+ {/* 4. 편의 */}
setHoveredCategory("편의")}
@@ -287,7 +320,7 @@ const CurationSection = ({
- 60
+ {scores.편의}
{hoveredCategory === "편의" && (
@@ -298,7 +331,7 @@ const CurationSection = ({
)}
- {/* 5. 소음 (Left-ish - 10) */}
+ {/* 5. 소음 */}
setHoveredCategory("소음")}
@@ -322,7 +355,7 @@ const CurationSection = ({
- 10
+ {scores.소음}
{hoveredCategory === "소음" && (
diff --git a/app/components/map/KakaoMap.tsx b/app/components/map/KakaoMap.tsx
index fd2e7a2..942aee3 100644
--- a/app/components/map/KakaoMap.tsx
+++ b/app/components/map/KakaoMap.tsx
@@ -12,6 +12,16 @@ import { cn } from "@/app/lib/utils";
import { FacilityItemInfraType } from "@/shared/api/generated/model/facilityItemInfraType";
import { FacilityItem } from "@/shared/api/generated/model/facilityItem";
+export interface Item {
+ natureType?: "WALK" | "PARK";
+ distanceMeters?: number;
+ id?: number;
+ name?: string;
+ latitude?: number;
+ longitude?: number;
+ population?: number;
+}
+
const CustomMarker = ({
lat,
lng,
@@ -93,6 +103,39 @@ const FacilityOverlay = ({
);
};
+const EnvironmentOverlay = ({
+ item,
+ onClose,
+}: {
+ item: Item;
+ onClose: () => void;
+}) => {
+ return (
+
e.stopPropagation()}
+ >
+
+
+ {item.name}
+
+
+
+
+
+ {item.distanceMeters &&
+ `${(item.distanceMeters / 1000).toFixed(1)}km`}
+
+
+ 거리에 있습니다.
+
+
+
+ );
+};
+
const KakaoMap = () => {
const {
isMapMode,
@@ -102,6 +145,10 @@ const KakaoMap = () => {
selectedInfra,
setSelectedInfra,
clearSelectedInfra,
+ environmentInfras,
+ selectedEnvironment,
+ setSelectedEnvironment,
+ clearSelectedEnvironment,
} = useMapModeStore();
const [map, setMap] = useState
(null);
@@ -116,6 +163,11 @@ const KakaoMap = () => {
"/map_marker/convenience/store_marker.svg",
};
+ const envIconMap: Record = {
+ WALK: "/map_marker/environment/mountain_marker.svg",
+ PARK: "/map_marker/environment/park_marker.svg",
+ };
+
// 기본 중심값: 약수역
const defaultCenter = { lat: 37.5545, lng: 127.0112 };
@@ -163,6 +215,7 @@ const KakaoMap = () => {
onCreate={setMap}
onClick={() => {
clearSelectedInfra();
+ clearSelectedEnvironment();
}}
>
{isMapMode ? (
@@ -210,6 +263,47 @@ const KakaoMap = () => {
)}
+ {/* 환경 정보 마커 */}
+ {environmentInfras.map((item) => {
+ const iconSrc = item.natureType
+ ? envIconMap[item.natureType]
+ : null;
+ if (!iconSrc || !item.latitude || !item.longitude) return null;
+
+ return (
+ setSelectedEnvironment(item)}
+ />
+ );
+ })}
+
+ {/* 환경 정보 커스텀 오버레이 */}
+ {selectedEnvironment &&
+ selectedEnvironment.latitude &&
+ selectedEnvironment.longitude && (
+
+ clearSelectedEnvironment()}
+ />
+
+ )}
+
{/* 종합데이터 모드: 선택된 매물만 map_marker.svg로 표시 */}
{selectedProperty && (
{
const { activeModules, toggleModule } = useModuleStore();
- const { selectedProperty } = useMapModeStore();
+ const {
+ selectedProperty,
+ setEnvironmentInfras,
+ clearEnvironmentInfras,
+ clearSelectedEnvironment,
+ environmentInfras,
+ } = useMapModeStore();
const isActive = activeModules.includes("environment");
- const score = selectedProperty?.propertyScores?.environmentScore || 0;
+
+ const { data: envData } = useGetEnvironmentTotal(
+ {
+ propertyId: selectedProperty?.id as number,
+ distance: 3000,
+ },
+ {
+ query: {
+ enabled: isActive && !!selectedProperty?.id,
+ },
+ },
+ );
+
+ useEffect(() => {
+ if (isActive && envData?.data?.items) {
+ setEnvironmentInfras(envData.data.items);
+ } else if (!isActive) {
+ clearEnvironmentInfras();
+ clearSelectedEnvironment();
+ }
+ }, [
+ isActive,
+ envData,
+ setEnvironmentInfras,
+ clearEnvironmentInfras,
+ clearSelectedEnvironment,
+ ]);
+
+ const score =
+ envData?.data?.environmentScore ||
+ selectedProperty?.propertyScores?.environmentScore ||
+ 0;
+ const pmData = envData?.data?.particulateMatter;
+
+ // 도보 시간 계산 (표준: 80m/min)
+ const calculateWalkTime = (meters?: number) => {
+ if (!meters) return "-";
+ const minutes = Math.ceil(meters / 80);
+ return `도보 ${minutes}분`;
+ };
+
+ const nearestPark = environmentInfras
+ .filter((item) => item.natureType === "PARK")
+ .sort((a, b) => (a.distanceMeters || 0) - (b.distanceMeters || 0))[0];
+
+ const nearestTrail = environmentInfras
+ .filter((item) => item.natureType === "WALK")
+ .sort((a, b) => (a.distanceMeters || 0) - (b.distanceMeters || 0))[0];
+
+ const parkTime = calculateWalkTime(nearestPark?.distanceMeters);
+ const trailTime = calculateWalkTime(nearestTrail?.distanceMeters);
return (
{
- 도보 3분
+ {parkTime}
@@ -144,7 +202,7 @@ const EnvironmentModule = () => {
- 도보 10분
+ {trailTime}
@@ -180,21 +238,21 @@ const EnvironmentModule = () => {
{[
{
name: "폐기물 수집",
- time: "도보 8분",
+ time: "-",
icon: "/icons/module/environment/factory.svg",
width: 15,
height: 15,
},
{
name: "하수처리장",
- time: "도보 10분",
+ time: "-",
icon: "/icons/module/environment/factory.svg",
width: 15,
height: 15,
},
{
name: "장례시설",
- time: "도보 15분",
+ time: "-",
icon: "/icons/module/environment/funeral.svg",
width: 14.571,
height: 14.571,
@@ -248,7 +306,7 @@ const EnvironmentModule = () => {
- 15
+ {pmData?.pm10 ?? 15}
㎍/㎥
@@ -276,7 +334,7 @@ const EnvironmentModule = () => {
- 21
+ {pmData?.pm25 ?? 21}
㎍/㎥
diff --git a/app/store/mapModeStore.ts b/app/store/mapModeStore.ts
index 6d28626..0217c05 100644
--- a/app/store/mapModeStore.ts
+++ b/app/store/mapModeStore.ts
@@ -1,6 +1,7 @@
import { create } from "zustand";
import { PropertyScore } from "@/shared/api/generated/model/propertyScore";
import { FacilityItem } from "@/shared/api/generated/model/facilityItem";
+import { Item } from "@/shared/api/generated/model/item";
interface SelectedProperty {
id: number;
@@ -29,6 +30,12 @@ interface MapModeState {
selectedInfra: FacilityItem | null;
setSelectedInfra: (infra: FacilityItem | null) => void;
clearSelectedInfra: () => void;
+ environmentInfras: Item[];
+ setEnvironmentInfras: (infras: Item[]) => void;
+ clearEnvironmentInfras: () => void;
+ selectedEnvironment: Item | null;
+ setSelectedEnvironment: (item: Item | null) => void;
+ clearSelectedEnvironment: () => void;
}
export const useMapModeStore = create((set) => ({
@@ -50,4 +57,11 @@ export const useMapModeStore = create((set) => ({
setSelectedInfra: (infra: FacilityItem | null) =>
set({ selectedInfra: infra }),
clearSelectedInfra: () => set({ selectedInfra: null }),
+ environmentInfras: [],
+ setEnvironmentInfras: (infras: Item[]) => set({ environmentInfras: infras }),
+ clearEnvironmentInfras: () => set({ environmentInfras: [] }),
+ selectedEnvironment: null,
+ setSelectedEnvironment: (item: Item | null) =>
+ set({ selectedEnvironment: item }),
+ clearSelectedEnvironment: () => set({ selectedEnvironment: null }),
}));
diff --git a/public/map_marker/environment/funeral_marker.svg b/public/map_marker/environment/funeral_marker.svg
new file mode 100644
index 0000000..936a99a
--- /dev/null
+++ b/public/map_marker/environment/funeral_marker.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/map_marker/environment/mountain_marker.svg b/public/map_marker/environment/mountain_marker.svg
new file mode 100644
index 0000000..9d9ec96
--- /dev/null
+++ b/public/map_marker/environment/mountain_marker.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/map_marker/environment/park_marker.svg b/public/map_marker/environment/park_marker.svg
new file mode 100644
index 0000000..97ab888
--- /dev/null
+++ b/public/map_marker/environment/park_marker.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/map_marker/environment/waste_marker.svg b/public/map_marker/environment/waste_marker.svg
new file mode 100644
index 0000000..ccc1c6b
--- /dev/null
+++ b/public/map_marker/environment/waste_marker.svg
@@ -0,0 +1,24 @@
+
diff --git a/shared/api/generated/model/item.ts b/shared/api/generated/model/item.ts
index b91d7a3..87a093e 100644
--- a/shared/api/generated/model/item.ts
+++ b/shared/api/generated/model/item.ts
@@ -7,8 +7,10 @@
*/
export interface Item {
- distanceMeter?: number;
+ natureType?: "WALK" | "PARK";
+ distanceMeters?: number;
id?: number;
+ name?: string;
latitude?: number;
longitude?: number;
population?: number;
From e35cfeedabc2612ae116ae402ae7518803c57ce7 Mon Sep 17 00:00:00 2001
From: HSCHEOL
Date: Tue, 10 Feb 2026 11:18:31 +0900
Subject: [PATCH 5/9] =?UTF-8?q?feat:=EC=A2=85=ED=95=A9=20=EB=8D=B0?=
=?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EB=AA=A8=EB=93=9C=20=EC=86=8C=EC=9D=8C=20?=
=?UTF-8?q?=EC=A2=85=ED=95=A9=20=EC=A0=90=EC=88=98=20API=20=EC=97=B0?=
=?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20=EB=A7=88=EC=BB=A4=20=ED=91=9C=EC=8B=9C?=
=?UTF-8?q?/=EB=A7=88=EC=BB=A4=20=EC=98=A4=EB=B2=84=EB=A0=88=EC=9D=B4=20?=
=?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/map/KakaoMap.tsx | 266 +++++++++++++++-
app/components/module/NoiseModule.tsx | 288 +++++++++++++-----
app/components/module/TimeSliderModule.tsx | 24 +-
app/store/mapModeStore.ts | 57 ++++
app/types/nearby-noise.ts | 23 ++
.../map_marker/noise/construction_marker.svg | 24 ++
.../map_marker/noise/firestation_marker.svg | 24 ++
public/map_marker/noise/hospital_marker.svg | 24 ++
public/map_marker/noise/noise_marker.svg | 32 ++
public/map_marker/noise/population_marker.svg | 32 ++
public/map_marker/noise/road_marker.svg | 24 ++
11 files changed, 722 insertions(+), 96 deletions(-)
create mode 100644 app/types/nearby-noise.ts
create mode 100644 public/map_marker/noise/construction_marker.svg
create mode 100644 public/map_marker/noise/firestation_marker.svg
create mode 100644 public/map_marker/noise/hospital_marker.svg
create mode 100644 public/map_marker/noise/noise_marker.svg
create mode 100644 public/map_marker/noise/population_marker.svg
create mode 100644 public/map_marker/noise/road_marker.svg
diff --git a/app/components/map/KakaoMap.tsx b/app/components/map/KakaoMap.tsx
index 942aee3..43e6957 100644
--- a/app/components/map/KakaoMap.tsx
+++ b/app/components/map/KakaoMap.tsx
@@ -11,6 +11,7 @@ import { useMapModeStore } from "../../store/mapModeStore";
import { cn } from "@/app/lib/utils";
import { FacilityItemInfraType } from "@/shared/api/generated/model/facilityItemInfraType";
import { FacilityItem } from "@/shared/api/generated/model/facilityItem";
+import { NearbyNoiseItem } from "@/app/types/nearby-noise";
export interface Item {
natureType?: "WALK" | "PARK";
@@ -20,6 +21,7 @@ export interface Item {
latitude?: number;
longitude?: number;
population?: number;
+ noise?: number;
}
const CustomMarker = ({
@@ -83,18 +85,15 @@ const FacilityOverlay = ({
}) => {
return (
e.stopPropagation()}
+ className="bg-[#FAF9FD] border border-[#745BCD] rounded-sm shadow-[0px_4px_4px_0px_rgba(0,0,0,0.15)] overflow-hidden w-50 cursor-pointer"
+ onClick={onClose}
>
-
+
-
+
{infra.address || "주소 정보가 없습니다."}
@@ -112,16 +111,13 @@ const EnvironmentOverlay = ({
}) => {
return (
e.stopPropagation()}
+ className="bg-[#F7FAF7] border border-[#29AD29] rounded-sm shadow-[0px_4px_4px_0px_rgba(0,0,0,0.15)] overflow-hidden w-45 cursor-pointer"
+ onClick={onClose}
>
@@ -136,6 +132,111 @@ const EnvironmentOverlay = ({
);
};
+const NoiseOverlay = ({
+ item,
+ districtAvg,
+ onClose,
+}: {
+ item: Item;
+ districtAvg: number;
+ onClose: () => void;
+}) => {
+ return (
+
+
+
현재 소음도:
+
+ {Math.round(item.noise || 0)}dB
+
+
+
+
+ 평균 소음도
+
+ {Math.round(districtAvg)}dB
+
+
+
+
+ );
+};
+
+const PopulationOverlay = ({
+ item,
+ districtAvg,
+ onClose,
+}: {
+ item: Item;
+ districtAvg: number;
+ onClose: () => void;
+}) => {
+ return (
+
+
+
현재 유동인구:
+
+ {item.population?.toLocaleString()}명
+
+
+
+
+ 평균 유동인구
+
+ {Math.round(districtAvg).toLocaleString()}명
+
+
+
+
+ );
+};
+
+const NearbyNoiseOverlay = ({
+ item,
+ onClose,
+}: {
+ item: NearbyNoiseItem;
+ onClose: () => void;
+}) => {
+ const isConstruction = item.noiseType === "CONSTRUCTION";
+ const headerColor = "#EA8B2B";
+
+ return (
+
+
+
+
+
+
+ {(item.distanceMeter / 1000).toFixed(1)}km
+
+
거리에 있습니다
+
+ {isConstruction && item.endDate && (
+
+ 공사 종료 예정: {item.endDate}
+
+ )}
+
+
+
+ );
+};
+
const KakaoMap = () => {
const {
isMapMode,
@@ -149,6 +250,20 @@ const KakaoMap = () => {
selectedEnvironment,
setSelectedEnvironment,
clearSelectedEnvironment,
+ noiseInfras,
+ populationInfras,
+ districtAvgNoise,
+ districtAvgPopulation,
+ selectedNoise,
+ setSelectedNoise,
+ clearSelectedNoise,
+ selectedPopulation,
+ setSelectedPopulation,
+ clearSelectedPopulation,
+ nearbyNoiseInfras,
+ selectedNearbyNoise,
+ setSelectedNearbyNoise,
+ clearSelectedNearbyNoise,
} = useMapModeStore();
const [map, setMap] = useState
(null);
@@ -168,6 +283,15 @@ const KakaoMap = () => {
PARK: "/map_marker/environment/park_marker.svg",
};
+ const noiseMarkerIcon = "/map_marker/noise/noise_marker.svg";
+ const populationMarkerIcon = "/map_marker/noise/population_marker.svg";
+
+ const nearbyNoiseIconMap: Record = {
+ FIRE_STATION: "/map_marker/noise/firestation_marker.svg",
+ EMERGENCY_CENTER: "/map_marker/noise/hospital_marker.svg",
+ CONSTRUCTION: "/map_marker/noise/construction_marker.svg",
+ };
+
// 기본 중심값: 약수역
const defaultCenter = { lat: 37.5545, lng: 127.0112 };
@@ -216,12 +340,14 @@ const KakaoMap = () => {
onClick={() => {
clearSelectedInfra();
clearSelectedEnvironment();
+ clearSelectedNoise();
+ clearSelectedPopulation();
}}
>
{isMapMode ? (
<>
{/* 종합데이터 모드: 편의 시설 마커 */}
- {convenienceInfras.map((infra) => {
+ {convenienceInfras.map((infra: FacilityItem) => {
const iconSrc = infra.infraType
? infraIconMap[infra.infraType]
: null;
@@ -264,7 +390,7 @@ const KakaoMap = () => {
)}
{/* 환경 정보 마커 */}
- {environmentInfras.map((item) => {
+ {environmentInfras.map((item: Item) => {
const iconSrc = item.natureType
? envIconMap[item.natureType]
: null;
@@ -324,6 +450,118 @@ const KakaoMap = () => {
}}
/>
)}
+
+ {noiseInfras.map((item: Item) => {
+ if (!item.latitude || !item.longitude) return null;
+ return (
+ setSelectedNoise(item)}
+ image={{
+ src: noiseMarkerIcon,
+ size: { width: 300, height: 300 },
+ options: {
+ offset: { x: 150, y: 150 },
+ },
+ }}
+ />
+ );
+ })}
+
+ {selectedNoise &&
+ selectedNoise.latitude &&
+ selectedNoise.longitude && (
+
+
+
+ )}
+
+ {/* 유동인구 정보 마커 */}
+ {populationInfras.map((item: Item) => {
+ if (!item.latitude || !item.longitude) return null;
+ return (
+ setSelectedPopulation(item)}
+ image={{
+ src: populationMarkerIcon,
+ size: { width: 300, height: 300 },
+ options: {
+ offset: { x: 150, y: 150 },
+ },
+ }}
+ />
+ );
+ })}
+
+ {selectedPopulation &&
+ selectedPopulation.latitude &&
+ selectedPopulation.longitude && (
+
+
+
+ )}
+
+ {/* 소음 발생 예상 구간 마커 */}
+ {nearbyNoiseInfras.map((item: NearbyNoiseItem) => {
+ const iconSrc = nearbyNoiseIconMap[item.noiseType];
+ if (!iconSrc) return null;
+
+ return (
+ setSelectedNearbyNoise(item)}
+ image={{
+ src: iconSrc,
+ size: { width: 44, height: 44 },
+ options: {
+ offset: { x: 22, y: 44 },
+ },
+ }}
+ />
+ );
+ })}
+
+ {selectedNearbyNoise && (
+
+
+
+ )}
>
) : (
<>
diff --git a/app/components/module/NoiseModule.tsx b/app/components/module/NoiseModule.tsx
index c7a01c0..7d0a45e 100644
--- a/app/components/module/NoiseModule.tsx
+++ b/app/components/module/NoiseModule.tsx
@@ -1,7 +1,12 @@
-import React from "react";
+import React, { useEffect } from "react";
import Image from "next/image";
import { useModuleStore } from "@/app/store/moduleStore";
import { useMapModeStore } from "@/app/store/mapModeStore";
+import {
+ useGetSmartPole,
+ useGetNearbyNoise,
+} from "@/shared/api/generated/noise-controller/noise-controller";
+import { NearbyNoiseData } from "@/app/types/nearby-noise";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
@@ -11,14 +16,148 @@ function cn(...inputs: ClassValue[]) {
const NoiseModule = () => {
const { activeModules, toggleModule } = useModuleStore();
- const { selectedProperty } = useMapModeStore();
+ const {
+ selectedProperty,
+ debouncedTime,
+ setNoiseInfras,
+ setPopulationInfras,
+ clearNoiseInfras,
+ clearPopulationInfras,
+ setDistrictAvgNoise,
+ setDistrictAvgPopulation,
+ setNearbyNoiseInfras,
+ clearNearbyNoiseInfras,
+ } = useMapModeStore();
const isActive = activeModules.includes("noise");
const score = selectedProperty?.propertyScores?.noiseScore || 0;
+ const { data: smartPoleData } = useGetSmartPole(
+ {
+ propertyId: selectedProperty?.id,
+ time: Math.floor(debouncedTime),
+ distance: 3000,
+ weekend: true,
+ } as unknown as Parameters[0],
+ {
+ query: {
+ enabled: !!selectedProperty?.id && isActive,
+ },
+ },
+ );
+
+ const { data: nearbyNoiseData } = useGetNearbyNoise(
+ {
+ propertyId: Number(selectedProperty?.id),
+ distance: 2000,
+ },
+ {
+ query: {
+ enabled: !!selectedProperty?.id && isActive,
+ },
+ },
+ );
+
+ useEffect(() => {
+ if (isActive && smartPoleData?.data?.noise?.items) {
+ setNoiseInfras(smartPoleData.data.noise.items);
+ if (smartPoleData.data.noise.districtAvgNoise !== undefined) {
+ setDistrictAvgNoise(smartPoleData.data.noise.districtAvgNoise);
+ }
+ } else if (!isActive) {
+ clearNoiseInfras();
+ }
+ }, [
+ isActive,
+ smartPoleData,
+ setNoiseInfras,
+ clearNoiseInfras,
+ setDistrictAvgNoise,
+ ]);
+
+ useEffect(() => {
+ if (isActive && smartPoleData?.data?.population?.items) {
+ setPopulationInfras(smartPoleData.data.population.items);
+ if (smartPoleData.data.population.districtAvgPopulation !== undefined) {
+ setDistrictAvgPopulation(
+ smartPoleData.data.population.districtAvgPopulation,
+ );
+ }
+ } else if (!isActive) {
+ clearPopulationInfras();
+ }
+ }, [
+ isActive,
+ smartPoleData,
+ setPopulationInfras,
+ clearPopulationInfras,
+ setDistrictAvgPopulation,
+ ]);
+
+ useEffect(() => {
+ const data = nearbyNoiseData?.data as unknown as NearbyNoiseData;
+ if (isActive && data?.items) {
+ setNearbyNoiseInfras(data.items);
+ } else if (!isActive) {
+ clearNearbyNoiseInfras();
+ }
+ }, [isActive, nearbyNoiseData, setNearbyNoiseInfras, clearNearbyNoiseInfras]);
+
+ const noiseInfo = smartPoleData?.data?.noise;
+ const populationInfo = smartPoleData?.data?.population;
+
+ // 도보 시간 계산 (표준: 80m/min)
+ const calculateWalkTime = (meters?: number) => {
+ if (meters === undefined) return "-";
+ const minutes = Math.ceil(meters / 80);
+ return `도보 ${minutes}분`;
+ };
+
+ const { nearbyNoiseInfras } = useMapModeStore();
+
+ const nearestFireStation = nearbyNoiseInfras
+ .filter((item) => item.noiseType === "FIRE_STATION")
+ .sort((a, b) => a.distanceMeter - b.distanceMeter)[0];
+
+ const nearestHospital = nearbyNoiseInfras
+ .filter((item) => item.noiseType === "EMERGENCY_CENTER")
+ .sort((a, b) => a.distanceMeter - b.distanceMeter)[0];
+
+ const nearestConstruction = nearbyNoiseInfras
+ .filter((item) => item.noiseType === "CONSTRUCTION")
+ .sort((a, b) => a.distanceMeter - b.distanceMeter)[0];
+
+ const nearbyItems = [
+ {
+ name: "소방서",
+ time: calculateWalkTime(nearestFireStation?.distanceMeter),
+ icon: "/icons/module/noise/fire.svg",
+ width: 14.75,
+ height: 14.585,
+ },
+ {
+ name: "대학병원",
+ time: calculateWalkTime(nearestHospital?.distanceMeter),
+ icon: "/icons/module/noise/hospital.svg",
+ width: 16.2,
+ height: 14.4,
+ },
+ {
+ name: nearestConstruction?.name || "공사현장",
+ time: calculateWalkTime(nearestConstruction?.distanceMeter),
+ icon: "/icons/module/noise/construction.svg",
+ sub: nearestConstruction?.endDate
+ ? `${nearestConstruction.endDate} 종료예정`
+ : undefined,
+ width: 15.6,
+ height: 15.63,
+ truncate: true,
+ },
+ ];
+
return (
{
{/* Header Section */}
@@ -35,7 +174,7 @@ const NoiseModule = () => {
{/* Icon Badge */}
@@ -63,7 +202,7 @@ const NoiseModule = () => {