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();