From 811ea1560bc2f13daf2c9a046f18cd16a26b2ddc Mon Sep 17 00:00:00 2001 From: ljpark Date: Sat, 13 Dec 2025 19:19:12 +0900 Subject: [PATCH 1/3] cm: port 3000 --- apps/cm/.env.local | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cm/.env.local b/apps/cm/.env.local index af93107b4..c6882cb70 100644 --- a/apps/cm/.env.local +++ b/apps/cm/.env.local @@ -1,6 +1,6 @@ # apps/cs/src/shared/config/env.ts 참고 -NEXT_PUBLIC_PORT=3001 -NEXT_PUBLIC_PROXY_HOST=http://localhost:3001 +NEXT_PUBLIC_PORT=3000 +NEXT_PUBLIC_PROXY_HOST=http://localhost:3000 NEXT_PUBLIC_SITE_ID=CM NEXT_PUBLIC_SUB_DOMAIN=http://localhost:13000 NEXT_PUBLIC_SERVER_API_URL=http://dev.onasys.co.kr:8000 From 373a5b6b0947db1aa40103d137bb597ca85d49c6 Mon Sep 17 00:00:00 2001 From: ljpark Date: Sat, 13 Dec 2025 19:34:12 +0900 Subject: [PATCH 2/3] iipbs/sn/dev merge --- apps/cm/src/pages/dashboard/model/map-data.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 apps/cm/src/pages/dashboard/model/map-data.ts diff --git a/apps/cm/src/pages/dashboard/model/map-data.ts b/apps/cm/src/pages/dashboard/model/map-data.ts new file mode 100644 index 000000000..a5eb34344 --- /dev/null +++ b/apps/cm/src/pages/dashboard/model/map-data.ts @@ -0,0 +1,65 @@ +export const basinKeyMap: { [key: string]: string } = { + "한강": "han", + "금강": "geum", + "낙동강": "nakdong", + "영산강": "yeongsan", +}; + +// --- 라벨 좌표(수동 고정값) : inset 적용 영역 기준 퍼센트 또는 px 사용 --- +// - 퍼센트: { x:0~1, y:0~1 } (권장: 반응형에 유리) +// - 픽셀: { px:number, py:number } (inset 좌상단 원점 기준) +export const MANUAL_LABEL_POS = { + han: { x: 0.52, y: 0.14 }, + geum: { x: 0.28, y: 0.47 }, + nakdong: { x: 0.64, y: 0.47 }, + yeongsan: { x: 0.18, y: 0.77 }, +}; + +export const BASIN_DATA = { + han: { + name: "한강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, + geum: { + name: "금강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, + nakdong: { + name: "낙동강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, + yeongsan: { + name: "영산강", + colTitle: "시설수", + rows: [ + ["500m³ 미만", 100000000], + ["500m³ 이상", 100000000], + ], + }, +}; + +export const BASIN_IDS = [ + { key: "han", labels: ["한강", "han"] }, + { key: "geum", labels: ["금강", "geum"] }, + { key: "nakdong", labels: ["낙동강", "nakdong"] }, + { key: "yeongsan", labels: ["영산강", "yeongsan"] }, +]; + +export const svcInfo = [ + {svcId: "wr", active: true, name:"물재이용"}, + {svcId: "sn", active: false, name:"하수관로"}, + {svcId: "fo", active: false, name:"운영실태"}, +] + \ No newline at end of file From 2d826674459f1438ff3ea7c21f6bfc59ddee38c7 Mon Sep 17 00:00:00 2001 From: ljpark Date: Sat, 13 Dec 2025 19:34:40 +0900 Subject: [PATCH 3/3] merge --- apps/cm/.env.local | 7 +- apps/cm/.env.production | 6 +- apps/cm/eslint.config.mjs | 5 + apps/cm/middleware.ts | 4 +- apps/cm/src/pages/dashboard/api/fo-service.ts | 36 ++- apps/cm/src/pages/dashboard/api/sn-service.ts | 67 +++- .../pages/dashboard/lib/korea-basin-map.tsx | 291 ++++++++--------- .../src/pages/dashboard/model/sn-interface.ts | 53 +++ .../src/pages/dashboard/ui/dashboard-page.tsx | 12 +- .../pages/dashboard/ui/dashboard-page2.tsx | 303 ------------------ .../src/pages/dashboard/ui/fo-fclt-cnt-rt.tsx | 2 +- .../pages/dashboard/ui/fo-wqlt-Infl-effl.tsx | 4 +- .../cm/src/pages/dashboard/ui/reuse-chart.tsx | 6 +- .../src/pages/dashboard/ui/reuse-status.tsx | 6 +- apps/cm/src/pages/dashboard/ui/sn-old-ppl.tsx | 23 +- .../src/pages/dashboard/ui/sn-ppl-status.tsx | 7 +- 16 files changed, 319 insertions(+), 513 deletions(-) delete mode 100644 apps/cm/src/pages/dashboard/ui/dashboard-page2.tsx diff --git a/apps/cm/.env.local b/apps/cm/.env.local index c6882cb70..5ce2a9fd7 100644 --- a/apps/cm/.env.local +++ b/apps/cm/.env.local @@ -7,6 +7,7 @@ NEXT_PUBLIC_SERVER_API_URL=http://dev.onasys.co.kr:8000 NEXT_PUBLIC_LINK_BACKEND_API_HOST=http://rsone.iptime.org:10014 NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 -NEXT_PUBLIC_SERVER_API_URL2=http://con.mind-one.co.kr:11083/ -# NEXT_PUBLIC_SERVER_API_URL2=http://192.168.1.222:8883/ -NEXT_PUBLIC_SERVER_API_URL_RSONE=https://apigateway.apps.rsonesoft.com/ \ No newline at end of file +NEXT_PUBLIC_SERVER_API_URL2=http://con.mind-one.co.kr:11083 +NEXT_PUBLIC_SERVER_API_URL_RSONE=https://apigateway.apps.rsonesoft.com/ +NEXT_PUBLIC_SN=https://devlion.hasuon.co.kr +NEXT_PUBLIC_WR=https://devtiger.hasuon.co.kr \ No newline at end of file diff --git a/apps/cm/.env.production b/apps/cm/.env.production index ec8b584b7..52e7e135c 100644 --- a/apps/cm/.env.production +++ b/apps/cm/.env.production @@ -4,4 +4,8 @@ NEXT_PUBLIC_PROXY_HOST=http://localhost:3001 NEXT_PUBLIC_SITE_ID=CS NEXT_PUBLIC_SUB_DOMAIN=http://localhost:13000 NEXT_PUBLIC_SERVER_API_URL=http://192.168.0.102:8000 -NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 \ No newline at end of file +NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2 + +NEXT_PUBLIC_SERVER_API_URL2=http://apigateway.iipbs-was.svc.cluster.local:8000 +NEXT_PUBLIC_SN=https://lion.hasuon.co.kr +NEXT_PUBLIC_WR=https://tiger.hasuon.co.kr \ No newline at end of file diff --git a/apps/cm/eslint.config.mjs b/apps/cm/eslint.config.mjs index 9109635e9..c9e490efd 100644 --- a/apps/cm/eslint.config.mjs +++ b/apps/cm/eslint.config.mjs @@ -12,6 +12,11 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), { + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname, + }, + }, rules: { "react-hooks/exhaustive-deps": "off", }, diff --git a/apps/cm/middleware.ts b/apps/cm/middleware.ts index 123cd3c5c..1cc23b8c2 100644 --- a/apps/cm/middleware.ts +++ b/apps/cm/middleware.ts @@ -58,7 +58,7 @@ export function middleware(req: NextRequest) { const userId = req.cookies.get(AUTH_USER_ID)?.value; // const decodedQuery = toDecodedQueryString(searchParams); - const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}${searchParams ? `?${searchParams}` : ""}`; + const newUrl = `${serverApiUrl2}${pathname.replace("/server", "")}${searchParams ? `?${searchParams}` : ""}`; // const newUrl = `${serverApiUrl2}${pathname}`; if (token) requestHeaders.set(CLAIM_NAME, token); @@ -96,7 +96,7 @@ export function middleware(req: NextRequest) { const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; // const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}`; - const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}${searchParams ? `?${searchParams}` : ""}`; + const newUrl = `${serverApiUrl2}${pathname.replace("/server", "")}${searchParams ? `?${searchParams}` : ""}`; if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); diff --git a/apps/cm/src/pages/dashboard/api/fo-service.ts b/apps/cm/src/pages/dashboard/api/fo-service.ts index 8dc163ffd..a35fedd51 100644 --- a/apps/cm/src/pages/dashboard/api/fo-service.ts +++ b/apps/cm/src/pages/dashboard/api/fo-service.ts @@ -50,14 +50,41 @@ export type OperFcltStateActions = { setOperFcltStatus: (operFcltStatus: OperFcltContent[] | null) => void; }; export type OperFcltStore = OperFcltState & OperFcltStateActions; +const initialOperFclt: OperFcltContent[]= [ + { + "cnt": 710, + "fctCpc": 2911609.0, + "rvbsn": "금강", + "gubun": "R" + }, + { + "cnt": 1032, + "fctCpc": 7506047.0, + "rvbsn": "낙동강", + "gubun": "R" + }, + { + "cnt": 1261, + "fctCpc": 2061610.0, + "rvbsn": "영산강", + "gubun": "R" + }, + { + "cnt": 983, + "fctCpc": 1.3229809E7, + "rvbsn": "한강", + "gubun": "R" + } + ]; export const defaultInitOperFcltState: OperFcltState = { - operFcltStatus: null, + operFcltStatus: initialOperFclt, }; export const createOperFcltStore = (initState: OperFcltState = defaultInitOperFcltState) => createStore()((set) => ({ ...initState, setOperFcltStatus: (operFcltStatus) => set(() => ({ operFcltStatus })), })); +export const operFcltStore = createOperFcltStore(); //수계별 유입ㆍ방류수질 분석 export type OperWqltState = { @@ -87,7 +114,12 @@ const initialWqltData: OperWqltContent[] = [ export const defaultInitOperWqltState: OperWqltState = { operWqltStatus: initialWqltData, }; -export const operFcltStore = createOperFcltStore(); +export const createOperWqltStore = (initState: OperWqltState = defaultInitOperWqltState) => + createStore()((set) => ({ + ...initState, + setOperWqltStatus: (operWqltStatus) => set(() => ({ operWqltStatus })), + })); +export const operWqltStore = createOperWqltStore(); // 물재이용 현황 export type ReuseState = { diff --git a/apps/cm/src/pages/dashboard/api/sn-service.ts b/apps/cm/src/pages/dashboard/api/sn-service.ts index 6e41b0724..789b46a9f 100644 --- a/apps/cm/src/pages/dashboard/api/sn-service.ts +++ b/apps/cm/src/pages/dashboard/api/sn-service.ts @@ -1,31 +1,69 @@ // apps/cm/src/pages/dashboard/api/sn-service.ts import { client } from "@/shared/api"; -import { createStore } from "zustand/vanilla"; +import { create } from "zustand"; import { ENDPOINT } from "@/shared/config"; import type { PplLengthContent, SewerageService } from "../model/sn-interface"; +import type { OldPplChartData } from "../model/sn-interface"; export type SnState = { snStatus: PplLengthContent[] | null; }; - export type SnStateActions = { setSnStatus: (snStatus: PplLengthContent[] | null) => void; }; - export type SnStore = SnState & SnStateActions; -export const defaultInitState: SnState = { - snStatus: null, +const initialOldPplChartData: OldPplChartData={ + labels: [ + "금강", + "한강", + "낙동강", + "영산강" + ], + datasets: [ + { + label: "20-25년", + data: [ + 1140, + 3181, + 3100, + 1662 + ], + backgroundColor: "#3B82F6", + borderRadius: 4 + }, + { + label: "25-30년", + data: [ + 1391, + 3615, + 1814, + 1411 + ], + backgroundColor: "#EF4444", + borderRadius: 4 + }, + { + label: "30년이상", + data: [ + 2014, + 10303, + 9831, + 2000 + ], + backgroundColor: "#F87171", + borderRadius: 4 + } + ] }; - -export const createSnStore = (initState: SnState = defaultInitState) => - createStore()((set) => ({ - ...initState, - setSnStatus: (snStatus) => set(() => ({ snStatus })), - })); - -// 전역으로 공유할 store 인스턴스 -// export const snStore = createSnStore(); +type OldPplStoreState = { + chartData: OldPplChartData; + setChartData: (chartData: OldPplChartData) => void; +}; +export const useOldPplStore = create((set) => ({ + chartData: initialOldPplChartData, + setChartData: (chartData) => set(() => ({ chartData })), +})); export class SewerageApiService implements SewerageService { // 기간별 관로 총길이 @@ -43,6 +81,7 @@ export class SewerageApiService implements SewerageService { ...item, sumPrlgVl: Number(item.sumPrlgVl), })); + return list; } diff --git a/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx b/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx index 1f1ec1ab7..00014cfa7 100644 --- a/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx +++ b/apps/cm/src/pages/dashboard/lib/korea-basin-map.tsx @@ -1,74 +1,22 @@ +"use client"; +/** + * 유역 지도 + */ import React, { useEffect, useRef } from "react"; import { useStore } from "zustand"; import styles from "./korea-basin-map.module.css"; // 추가 import { basinStore } from "../api/fo-service"; +import { basinKeyMap,MANUAL_LABEL_POS } from "../model/map-data"; +import { BASIN_DATA, BASIN_IDS } from "../model/map-data"; +// import { operFcltStore } from "../api/fo-service"; +// import {Button} from "@common/components"; const SVG_URL = "/images/region_map.svg"; // public 폴더에 region_map.svg 위치 필요 - // 2. 유역 키를 매핑하는 로직 (예: "한강" -> "han") -export const basinKeyMap: { [key: string]: string } = { - "한강": "han", - "금강": "geum", - "낙동강": "nakdong", - "영산강": "yeongsan", -}; -// --- 라벨 좌표(수동 고정값) : inset 적용 영역 기준 퍼센트 또는 px 사용 --- -// - 퍼센트: { x:0~1, y:0~1 } (권장: 반응형에 유리) -// - 픽셀: { px:number, py:number } (inset 좌상단 원점 기준) -const MANUAL_LABEL_POS = { - han: { x: 0.52, y: 0.14 }, - geum: { x: 0.28, y: 0.47 }, - nakdong: { x: 0.64, y: 0.47 }, - yeongsan: { x: 0.18, y: 0.77 }, -}; - -const BASIN_DATA = { - han: { - name: "한강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, - geum: { - name: "금강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, - nakdong: { - name: "낙동강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, - yeongsan: { - name: "영산강", - colTitle: "시설수", - rows: [ - ["500m³ 미만", 100000000], - ["500m³ 이상", 100000000], - ], - }, -}; - -const BASIN_IDS = [ - { key: "han", labels: ["한강", "han"] }, - { key: "geum", labels: ["금강", "geum"] }, - { key: "nakdong", labels: ["낙동강", "nakdong"] }, - { key: "yeongsan", labels: ["영산강", "yeongsan"] }, -]; - function fmt(n: number) { return new Intl.NumberFormat("ko-KR").format(n); } - export function KoreaBasinMap() { const stageRef = useRef(null); const svgLayerRef = useRef(null); @@ -76,10 +24,12 @@ export function KoreaBasinMap() { const popupRef = useRef(null); const badgeRef = useRef(null); const rowsRef = useRef(null); - const colTitleRef = useRef(null); - + const colTitleRef = useRef(null); + // const [services, setServices] = useState(svcInfo); const { selectedBasin, setSelectedBasin } = useStore(basinStore); + // const { operFcltStatus, setSelectedBasin } = useStore(operFcltStore); const selectedBasinKey = basinKeyMap[selectedBasin]; + // selectedBasinKey prop이 변경될 때 지도를 업데이트하는 useEffect useEffect(() => { if (selectedBasinKey) { @@ -102,12 +52,12 @@ export function KoreaBasinMap() { // console.log("Loading SVG from:", SVG_URL); fetch(SVG_URL) .then((r) => { - console.log("SVG fetch response:", r.status, r.statusText); + // console.log("SVG fetch response:", r.status, r.statusText); if (!r.ok) throw new Error(`HTTP error! status: ${r.status}`); return r.text(); }) .then((txt) => { - console.log("SVG content loaded, length:", txt.length); + // console.log("SVG content loaded, length:", txt.length); if (!svgLayerRef.current) { console.error("svgLayerRef.current is null"); return; @@ -118,7 +68,7 @@ export function KoreaBasinMap() { console.error("SVG element not found in loaded content"); return; } - console.log("SVG element found:", svg); + // console.log("SVG element found:", svg); // viewBox/크기 세팅 if (!svg.hasAttribute("viewBox")) { @@ -138,13 +88,13 @@ export function KoreaBasinMap() { // 1) 지도 그룹 수집 gs = []; - console.log("Looking for basin groups in SVG..."); + // console.log("Looking for basin groups in SVG..."); BASIN_IDS.forEach((item) => { let g: SVGGElement | null = null; for (const id of item.labels) { g = svg!.getElementById(id) as SVGGElement | null; if (g) { - console.log(`Found group for ${item.key} with id '${id}':`, g); + // console.log(`Found group for ${item.key} with id '${id}':`, g); break; } } @@ -167,20 +117,20 @@ export function KoreaBasinMap() { gs.push(g); }); - console.log(`Found ${gs.length} basin groups`); + // console.log(`Found ${gs.length} basin groups`); // 각 유역 그룹의 패스 정보 확인 - gs.forEach((g, index) => { - const paths = g.querySelectorAll('path'); - console.log(`Group ${index} (${(g as SVGGElement & { dataset: { key: string } }).dataset.key}):`, { - element: g, - pathCount: paths.length, - paths: Array.from(paths).map(p => ({ - className: p.className.baseVal, - d: p.getAttribute('d')?.substring(0, 50) + '...' - })) - }); - }); + // gs.forEach((g, index) => { + // const paths = g.querySelectorAll('path'); + // // console.log(`Group ${index} (${(g as SVGGElement & { dataset: { key: string } }).dataset.key}):`, { + // // element: g, + // // pathCount: paths.length, + // // paths: Array.from(paths).map(p => ({ + // // className: p.className.baseVal, + // // d: p.getAttribute('d')?.substring(0, 50) + '...' + // // })) + // // }); + // }); // 2) 함수들 먼저 정의 const clear = () => { @@ -204,7 +154,7 @@ export function KoreaBasinMap() { // 유역정보 function showFor(g: SVGGElement) { - console.log("showFor called for:", g); + // console.log("showFor called for:", g); const key = (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.key; const data = (BASIN_DATA as Record)[key] || { name: (g as SVGGElement & { dataset: { label: string } }).dataset.label, @@ -230,7 +180,7 @@ export function KoreaBasinMap() { const cx = (r.left + r.right) / 2 - sRect.left; const ty = r.top - sRect.top; - console.log("Popup position:", { cx, ty }); + // console.log("Popup position:", { cx, ty }); if (badgeRef.current) { badgeRef.current.style.left = cx + "px"; @@ -244,7 +194,7 @@ export function KoreaBasinMap() { if (styles.show) { popupRef.current.classList.add(styles.show); } - console.log("Added show class to popup:", popupRef.current.classList.toString()); + // console.log("Added show class to popup:", popupRef.current.classList.toString()); } if (badgeRef.current && styles.show) { badgeRef.current.classList.add(styles.show); @@ -262,7 +212,7 @@ export function KoreaBasinMap() { } function showForWithLabel(g: SVGGElement) { - console.log("showForWithLabel called for:", g); + // console.log("showForWithLabel called for:", g); showFor(g); const idx = gs.indexOf(g); labels.forEach((l) => l.classList.remove("active")); @@ -382,81 +332,110 @@ export function KoreaBasinMap() { }; }, []); + // const handleSvcInfoClick = (clickedSvcId: string) => { + // setServices(prevServices => + // prevServices.map(svc => ({ + // ...svc, + // active: svc.svcId === clickedSvcId, + // })) + // ); + // }; // 스타일은 필요에 따라 tailwind, css 등으로 커스터마이즈하세요. return ( -
-
-
-
-
-