diff --git a/apps/sn/.env.development b/apps/sn/.env.development
index 8e9cb096e..cd4879013 100644
--- a/apps/sn/.env.development
+++ b/apps/sn/.env.development
@@ -1,3 +1,4 @@
+# 공단 개발
NEXT_PUBLIC_SITE_ID = SN
NEXT_PUBLIC_PORT = 3000
NEXT_PUBLIC_CLAIM_NAME = Authorization
@@ -10,4 +11,4 @@ NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
-NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
\ No newline at end of file
+NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
diff --git a/apps/sn/.env.local b/apps/sn/.env.local
index ed1b605c3..90543d02f 100644
--- a/apps/sn/.env.local
+++ b/apps/sn/.env.local
@@ -5,15 +5,14 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_PORT = 3002
NEXT_PUBLIC_PROXY_HOST = http://localhost:3002
+# NEXT_PUBLIC_PROXY_HOST = http://sn.onasys.co.kr:3002
NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
-NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083
+# NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083
NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com
-NEXT_PUBLIC_DEV_CM_LOGIN_PAGE_URL = http://localhost:3000/auth/login
-NEXT_PUBLIC_VDI_DEV_CM_LOGIN_PAGE_URL = http://localhost:3000/auth/login
-NEXT_PUBLIC_VDI_DEV_CM_LOGIN_PAGE_URL = http://localhost:3000/auth/login
+NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.222:8883
-#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883
\ No newline at end of file
+NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883
\ No newline at end of file
diff --git a/apps/sn/.env.production b/apps/sn/.env.production
index d8c2e00e9..c46ea814c 100644
--- a/apps/sn/.env.production
+++ b/apps/sn/.env.production
@@ -1,3 +1,4 @@
+# 공단 운영
NEXT_PUBLIC_SITE_ID = SN
NEXT_PUBLIC_PORT = 3000
NEXT_PUBLIC_CLAIM_NAME = Authorization
@@ -10,4 +11,5 @@ NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
-NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
\ No newline at end of file
+NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
+NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
diff --git a/apps/sn/app/(with-base-layout)/ii/test/page.tsx b/apps/sn/app/(with-base-layout)/ii/test/page.tsx
new file mode 100644
index 000000000..9e11fccbb
--- /dev/null
+++ b/apps/sn/app/(with-base-layout)/ii/test/page.tsx
@@ -0,0 +1,105 @@
+"use client";
+
+import React from "react";
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+ type ChartOptions,
+} from "chart.js";
+import { Bar } from "react-chartjs-2";
+
+ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
+
+export default function Page() {
+ const labels = ["한강", "낙동강", "영산강", "금강"];
+
+ // 데이터: [기타, 오수관로, 우수관로]
+ const datasets = [
+ {
+ label: "기타",
+ data: [15481, 6959, 1938, 1967],
+ backgroundColor: "rgba(255, 178, 150, 0.9)", // 연한 주황(파스텔톤)
+ borderColor: "rgba(255,178,150,1)",
+ },
+ {
+ label: "오수관로",
+ data: [15878, 16336, 5670, 8073],
+ backgroundColor: "rgba(180, 240, 190, 0.95)", // 연한 초록(파스텔톤)
+ borderColor: "rgba(180,240,190,1)",
+ },
+ {
+ label: "우수관로",
+ data: [12892, 10491, 4280, 4838],
+ backgroundColor: "rgba(170, 210, 245, 0.95)", // 연한 파랑(파스텔톤)
+ borderColor: "rgba(170,210,245,1)",
+ },
+ ];
+
+ // 총합(라벨당) 계산
+ const totals = labels.map((_, i) =>
+ datasets.reduce((s, ds) => s + (ds.data[i] as number), 0),
+ );
+
+ const options: ChartOptions<"bar"> = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ position: "top",
+ },
+ tooltip: {
+ callbacks: {
+ label: (ctx) => `${ctx.dataset.label}: ${Number(ctx.parsed.y).toLocaleString()}km`,
+ },
+ },
+ },
+ scales: {
+ x: {
+ stacked: true,
+ },
+ y: {
+ stacked: true,
+ beginAtZero: true,
+ ticks: {
+ callback: (value) => `${Number(value).toLocaleString()}km`,
+ },
+ },
+ },
+ // 각 스택 위에 총합 표시
+ animation: {
+ onComplete: function (animation) {
+ const chart = animation.chart;
+ const ctx = chart.ctx;
+ const lastMeta = chart.getDatasetMeta(datasets.length - 1);
+ ctx.save();
+ ctx.fillStyle = "#111";
+ ctx.font = "gray bold 12px Arial";
+ ctx.textAlign = "center";
+
+ totals.forEach((total, i) => {
+ const bar = lastMeta.data[i];
+ if (!bar) return;
+ const x = bar.x;
+ // y는 마지막 스택의 y 좌표(스택의 최상단)에서 조금 위로
+ const y = bar.y - 8;
+ ctx.fillText(`총 ${total.toLocaleString()}km`, x, y);
+ });
+
+ ctx.restore();
+ },
+ },
+ };
+
+ const data = { labels, datasets };
+
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/sn/app/(with-base-layout)/ii/test/page_bar.tsx b/apps/sn/app/(with-base-layout)/ii/test/page_bar.tsx
new file mode 100644
index 000000000..08b8755c8
--- /dev/null
+++ b/apps/sn/app/(with-base-layout)/ii/test/page_bar.tsx
@@ -0,0 +1,143 @@
+"use client";
+
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend,
+ type ChartOptions,
+} from "chart.js";
+import { Bar } from "react-chartjs-2";
+import { Card, CardHeader, CardTitle, CardContent } from "@common/business_components/ui/chart/lib/card";
+import { Button } from "@common/components";
+
+// ChartJS 컴포넌트 등록
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ Title,
+ Tooltip,
+ Legend
+);
+
+export default function TestPage() {
+ // 차트 데이터
+ const labels = ["한강", "낙동강", "영산강", "금강"];
+
+ const datasets = [
+ {
+ label: '20-25년',
+ data: [3859, 1066, 1929, 2828],
+ backgroundColor: '#3B82F6', // 파란색
+ borderRadius: 4,
+ },
+ {
+ label: '25년-30년',
+ data: [3153, 1420, 1204, 2877],
+ backgroundColor: '#EF4444', // 빨간색
+ borderRadius: 4,
+ },
+ {
+ label: '30년이상',
+ data: [9404, 1835, 1821, 8115],
+ backgroundColor: '#F87171', // 연한 빨간색/분홍색
+ borderRadius: 4,
+ },
+ ];
+
+ // 각 지역별 총 길이 계산
+ const totalLengths = [44251, 33786, 11888, 14878];
+
+ // 차트 옵션 설정
+ const options: ChartOptions<"bar"> = {
+ responsive: true,
+ maintainAspectRatio: false,
+ scales: {
+ x: {
+ grid: {
+ display: true,
+ },
+ },
+ y: {
+ beginAtZero: true,
+ min: 0,
+ max: 10000,
+ ticks: {
+ stepSize: 2000,
+ callback: function(value) {
+ return value + 'km';
+ }
+ },
+ grid: {
+ color: 'rgba(200, 200, 200, 0.3)',
+ }
+ }
+ },
+ plugins: {
+ legend: {
+ position: 'top',
+ align: 'start',
+ labels: {
+ boxWidth: 15,
+ padding: 15,
+ usePointStyle: false,
+ },
+ },
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ return `${context.dataset.label}: ${context.parsed.y}km`;
+ }
+ }
+ },
+ },
+ // 총 길이 데이터 표시를 위한 애니메이션 후 함수 설정
+ animation: {
+ onComplete: function(animation) {
+ const chart = animation.chart;
+ const ctx = chart.ctx;
+
+ // 각 데이터셋의 마지막 데이터셋의 인덱스
+ const lastDatasetIndex = datasets.length - 1;
+
+ // 텍스트 스타일 설정
+ ctx.font = '11px Arial';
+ ctx.fillStyle = '#333';
+ ctx.textAlign = 'center';
+
+ // 각 지역별로 총 길이 표시
+ labels.forEach((_, i) => {
+ const xPos = chart.scales.x ? chart.scales.x.getPixelForTick(i) : 0;
+ const yPos = chart.chartArea.top; // 차트 영역 최상단 위에 표시
+
+ // 막대 위에 총 길이 텍스트 표시
+ if (totalLengths[i]) ctx.fillText(`총 ${totalLengths[i].toLocaleString()}km`, xPos, yPos);
+ });
+ }
+ }
+ };
+
+ const data = {
+ labels,
+ datasets,
+ };
+
+ return (
+
+
+ 노후하수관로현황
+
+
+
+ {/* 너비 400px, 높이 150px로 조정 */}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/apps/sn/app/(with-base-layout)/ii/test/page_pie.tsx b/apps/sn/app/(with-base-layout)/ii/test/page_pie.tsx
new file mode 100644
index 000000000..2cd6c1b95
--- /dev/null
+++ b/apps/sn/app/(with-base-layout)/ii/test/page_pie.tsx
@@ -0,0 +1,146 @@
+// ...existing code...
+"use client";
+
+import React from "react";
+import { Chart as ChartJS, ArcElement, Tooltip, Legend, type ChartOptions } from "chart.js";
+import { Pie } from "react-chartjs-2";
+
+ChartJS.register(ArcElement, Tooltip, Legend);
+
+export default function Page() {
+ const labels = ["한강", "낙동강", "영산강", "금강"];
+ const datasets = [
+ { label: "20년 미만", data: [27835, 29465, 6934, 1058], color: "rgba(169, 169, 169, 0.8)" },
+ { label: "20-25년", data: [3859, 1066, 1929, 2828], color: "rgba(255, 160, 122, 0.8)" },
+ { label: "25년-30년", data: [3153, 1420, 1204, 2877], color: "rgba(152, 251, 152, 0.8)" },
+ { label: "30년이상", data: [9404, 1835, 1821, 8115], color: "rgba(135, 206, 235, 0.8)" },
+ ];
+ // 각 지역별 총 길이 계산
+ const totalLengths = [44251, 33786, 11888, 14878];
+ // 외부 툴팁을 사용하여 컨테이너 넘침과 관계없이 전체 툴팁을 보여주고,
+ // 각 항목의 전체에서 차지하는 비율(%)을 표시합니다.
+ const pieOptions: ChartOptions<"pie"> = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: { display: false },
+ tooltip: {
+ enabled: false, // 기본 캔버스 툴팁 비활성화 (외부 툴팁 사용)
+ external: function (context) {
+ const { chart, tooltip } = context;
+ let tooltipEl = document.getElementById("chartjs-external-tooltip");
+
+ // tooltip DOM이 없으면 생성
+ if (!tooltipEl) {
+ tooltipEl = document.createElement("div");
+ tooltipEl.id = "chartjs-external-tooltip";
+ tooltipEl.style.position = "absolute";
+ tooltipEl.style.pointerEvents = "auto";
+ tooltipEl.style.background = "rgba(0,0,0,0.75)";
+ tooltipEl.style.color = "#fff";
+ tooltipEl.style.padding = "6px 8px";
+ tooltipEl.style.borderRadius = "6px";
+ tooltipEl.style.fontSize = "12px";
+ tooltipEl.style.zIndex = "9999";
+ tooltipEl.style.transform = "translate(-50%, -120%)";
+ document.body.appendChild(tooltipEl);
+ }
+
+ // 사라짐 처리
+ if (tooltip.opacity === 0) {
+ tooltipEl.style.opacity = "0";
+ return;
+ }
+
+ // 툴팁 내용 구성 (파이의 전체 합 대비 비율 표시)
+ const data = chart.data;
+ const dataset = data.datasets && data.datasets[0] && data.datasets[0].data
+ ? (data.datasets[0].data as number[])
+ : [];
+ const total = dataset.reduce((s, n) => s + (Number(n) || 0), 0);
+
+ const innerHtmlParts: string[] = [];
+ if (tooltip.dataPoints) {
+ tooltip.dataPoints.forEach((dp) => {
+ const idx = dp.dataIndex;
+ const label = (data.labels && data.labels[idx]) || dp.label || "";
+ const value = Number(dp.raw ?? dp.parsed ?? 0);
+ const pct = total > 0 ? (value / total) * 100 : 0;
+ innerHtmlParts.push(
+ `
+
+ ${label}: ${value.toLocaleString()}km
+ (${pct.toFixed(1)}%)
+
`
+ );
+ });
+ }
+
+ tooltipEl.innerHTML = innerHtmlParts.join("");
+ tooltipEl.style.opacity = "1";
+
+ // 위치: 차트 캔버스 기준 툴팁 caret 위치에 맞춤 (윈도우 스크롤 보정 포함)
+ const canvasRect = chart.canvas.getBoundingClientRect();
+ const caretX = tooltip.caretX ?? (canvasRect.left + canvasRect.width / 2);
+ const caretY = tooltip.caretY ?? (canvasRect.top + canvasRect.height / 2);
+ tooltipEl.style.left = `${canvasRect.left + window.pageXOffset + caretX}px`;
+ tooltipEl.style.top = `${canvasRect.top + window.pageYOffset + caretY}px`;
+ },
+ },
+ },
+ };
+
+
+ // 상단에 공통 범례(연도) 가로로 표시
+ const SharedLegend = () => (
+
+ {datasets.map((d) => (
+
+
+ {d.label}
+
+ ))}
+
+ );
+
+ // 레이블(강) 당 pie 데이터 생성 (레이블은 아래로 이동)
+ const Charts = () =>
+ labels.map((river, i) => {
+ const data = {
+ labels: datasets.map((d) => d.label),
+ datasets: [
+ {
+ data: datasets.map((d) => d.data[i]),
+ backgroundColor: datasets.map((d) => d.color),
+ borderColor: datasets.map(() => "rgba(255,255,255,0.8)"),
+ borderWidth: 1,
+ },
+ ],
+ };
+
+ return (
+
+
+ {/* 레이블을 차트 하단으로 이동 */}
+
{totalLengths[i]?.toLocaleString()} km
+
{river}
+
+ );
+ });
+
+ return (
+
+ {/* 상단 공통 범례 */}
+
+
+
+
+
+
+
+
+ );
+}
+// ...existing code...
\ No newline at end of file
diff --git a/apps/sn/src/entities/auth/lib/auth-wrapper.tsx b/apps/sn/src/entities/auth/lib/auth-wrapper.tsx
index 5d93b000d..1d722ff48 100644
--- a/apps/sn/src/entities/auth/lib/auth-wrapper.tsx
+++ b/apps/sn/src/entities/auth/lib/auth-wrapper.tsx
@@ -10,7 +10,7 @@ import { getCookie } from "@/shared/lib";
import { LoginApi } from "../api/login-service";
-const LOGIN_PAGE_URL = process.env.NEXT_PUBLIC_DEV_CM_LOGIN_PAGE_URL || "http://localhost:3000/auth/login"; // 로그인 페이지 URL
+const LOGIN_PAGE_URL = process.env.NEXT_PUBLIC_CM_LOGIN_PAGE_URL || "http://dev.onasys.co.kr:3000/auth/login"; // 로그인 페이지 URL
const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
const router = useRouter();