Compare commits
3
Commits
main
...
iipbs/sn/ii
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
738b1f7735 | ||
|
|
3381e16573 | ||
|
|
1b57670830 |
@@ -10,4 +10,5 @@ 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_DC_URL = https://apigateway.apps.rsonesoft.com
|
||||
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083
|
||||
+3
-4
@@ -4,16 +4,15 @@ NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
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://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_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
|
||||
@@ -11,3 +11,4 @@ NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BE
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
|
||||
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
|
||||
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
|
||||
@@ -0,0 +1,14 @@
|
||||
NEXT_PUBLIC_SITE_ID = SN
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
NEXT_PUBLIC_PORT = 3000
|
||||
NEXT_PUBLIC_PROXY_HOST = https://sn.hasuon.or.kr
|
||||
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_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000
|
||||
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
|
||||
NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login
|
||||
@@ -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 (
|
||||
<div style={{ width: "100%", maxWidth: 800, height: 360 }}>
|
||||
<Bar options={options} data={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-base">노후하수관로현황</CardTitle>
|
||||
<Button size="sm">더보기</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-4">
|
||||
{/* 너비 400px, 높이 150px로 조정 */}
|
||||
<div className="w-[400px] h-[150px] mx-auto">
|
||||
<Bar options={options} data={data} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -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(
|
||||
`<div style="white-space:nowrap">
|
||||
<strong style="display:inline-block;width:8px;height:8px;border-radius:2px;background:${(dp as any).element?.options?.backgroundColor || (dataset as any)[idx] || "#999"};margin-right:6px;vertical-align:middle"></strong>
|
||||
<span style="vertical-align:middle">${label}: ${value.toLocaleString()}km</span>
|
||||
<span style="margin-left:8px;color:#ddd">(${pct.toFixed(1)}%)</span>
|
||||
</div>`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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 = () => (
|
||||
<div className="flex gap-4 items-center justify-center">
|
||||
{datasets.map((d) => (
|
||||
<div key={d.label} className="flex items-center gap-2">
|
||||
<span style={{ width: 12, height: 12, backgroundColor: d.color, borderRadius: 3, display: "inline-block" }} />
|
||||
<span className="text-xl text-gray-600">{d.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
// 레이블(강) 당 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 (
|
||||
<div key={river} className="flex flex-col items-center gap-2">
|
||||
<div style={{ width: 80, height: 80 }}>
|
||||
<Pie data={data} options={pieOptions} />
|
||||
</div>
|
||||
{/* 레이블을 차트 하단으로 이동 */}
|
||||
<div className="text-lg font-medium text-gray-600">{totalLengths[i]?.toLocaleString()} km</div>
|
||||
<div className="text-xl font-bold text-gray-600">{river}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-4 w-[400px]">
|
||||
{/* 상단 공통 범례 */}
|
||||
<div className="mb-3">
|
||||
<SharedLegend />
|
||||
</div>
|
||||
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<Charts />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// ...existing code...
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user