cm:물재이용 api 호출
This commit is contained in:
+2
-1
@@ -7,4 +7,5 @@ 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://con.mind-one.co.kr:11083/
|
||||
NEXT_PUBLIC_SERVER_API_URL2=http://192.168.1.222:8883/
|
||||
+31
-23
@@ -14,11 +14,11 @@ const serverApiUrl =
|
||||
const serverApiUrl2 =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL2 ?? serverApiUrl;
|
||||
|
||||
const serverApiUrl3 =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL3 ?? serverApiUrl;
|
||||
// const serverApiUrl3 =
|
||||
// process.env.NEXT_PUBLIC_SERVER_API_URL3 ?? serverApiUrl;
|
||||
|
||||
const serverApiUrl4 =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL4 ?? serverApiUrl;
|
||||
// const serverApiUrl4 =
|
||||
// process.env.NEXT_PUBLIC_SERVER_API_URL4 ?? serverApiUrl;
|
||||
|
||||
const serverApiUrl5 =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL5 ?? serverApiUrl;
|
||||
@@ -33,19 +33,6 @@ export function middleware(req: NextRequest) {
|
||||
`[Request]\n Original Pathname: ${pathname}`,
|
||||
];
|
||||
|
||||
if (pathname.startsWith("/server")) {
|
||||
const queryParams = searchParams.toString();
|
||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||
queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/attach-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
@@ -57,7 +44,7 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
// console.log(logMessages.join("\n"));
|
||||
|
||||
return NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders,
|
||||
@@ -87,7 +74,7 @@ export function middleware(req: NextRequest) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
const userId = req.cookies.get(AUTH_USER_ID)?.value;
|
||||
const newUrl = `${serverApiUrl3}${pathname}`;
|
||||
const newUrl = `${serverApiUrl2}${pathname}`;
|
||||
|
||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||
@@ -101,11 +88,12 @@ export function middleware(req: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/operation-service")) {
|
||||
if (pathname.startsWith("/server/operation-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
const userId = req.cookies.get(AUTH_USER_ID)?.value;
|
||||
const newUrl = `${serverApiUrl4}${pathname}`;
|
||||
const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}`;
|
||||
console.log("==========================================" + newUrl);
|
||||
|
||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||
@@ -163,11 +151,25 @@ export function middleware(req: NextRequest) {
|
||||
|
||||
logMessages.push(` User Service Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
// console.log(logMessages.join("\n"));
|
||||
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/server")) {
|
||||
const queryParams = searchParams.toString();
|
||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||
queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
// console.log(logMessages.join("\n"));
|
||||
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
|
||||
if (pathname === "/") {
|
||||
return NextResponse.redirect(new URL("/dashboard", req.url));
|
||||
}
|
||||
@@ -178,5 +180,11 @@ export function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/server/:path*", "/attach-service/:path*", "/user-service/:path*", "/"],
|
||||
matcher: ["/server/:path*",
|
||||
"/attach-service/:path*",
|
||||
"/user-service/:path*",
|
||||
"/sewerage-service/:path*",
|
||||
"/reuse-service/:path*",
|
||||
"/operation-service/:path*",
|
||||
"/"],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// apps/cm/src/pages/dashboard/api/fo-service.ts
|
||||
import { client } from "@/shared/api";
|
||||
import { createStore } from "zustand/vanilla";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { ReuseStatusContent, OperationService } from "../model/fo-interface";
|
||||
|
||||
export type ReuseState = {
|
||||
reuseStatus: ReuseStatusContent[] | null;
|
||||
};
|
||||
|
||||
export type ReuseStateActions = {
|
||||
setReuseStatus: (reuseStatus: ReuseStatusContent[] | null) => void;
|
||||
};
|
||||
|
||||
export type ReuseStore = ReuseState & ReuseStateActions;
|
||||
|
||||
export const defaultInitState: ReuseState = {
|
||||
reuseStatus: null,
|
||||
};
|
||||
|
||||
export const createReuseStore = (initState: ReuseState = defaultInitState) =>
|
||||
createStore<ReuseStore>()((set) => ({
|
||||
...initState,
|
||||
setReuseStatus: (reuseStatus) => set(() => ({ reuseStatus })),
|
||||
}));
|
||||
|
||||
// 전역으로 공유할 store 인스턴스
|
||||
export const reuseStore = createReuseStore();
|
||||
|
||||
export class OperationApiService implements OperationService {
|
||||
// 여러 컴포넌트에서 호출해도 서버 호출은 1번만 되도록 캐싱
|
||||
public async getReuseStatus(): Promise<ReuseStatusContent[]> {
|
||||
const { reuseStatus, setReuseStatus } = reuseStore.getState();
|
||||
if (reuseStatus && reuseStatus.length > 0) {
|
||||
return reuseStatus;
|
||||
}
|
||||
|
||||
const response = await client.get<{
|
||||
status: number;
|
||||
data: ReuseStatusContent[];
|
||||
count: number;
|
||||
}>(ENDPOINT.OPERATION_SERVICE.REUSE_STATUS);
|
||||
|
||||
const list = (response.data.data ?? []).map((item) => ({
|
||||
...item,
|
||||
reclaimed: Number(item.reclaimed),
|
||||
rainwater: Number(item.rainwater),
|
||||
}));
|
||||
|
||||
setReuseStatus(list);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
export const OperationApi = new OperationApiService();
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface ReuseStatusContent {
|
||||
yr: string;
|
||||
sewage: number;
|
||||
reclaimed: number;
|
||||
rainwater: number;
|
||||
}
|
||||
|
||||
export interface OperationService {
|
||||
getReuseStatus(): Promise<ReuseStatusContent[]>;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
/**
|
||||
* 수계별 시설운영 개소수 및 가동률
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
/**
|
||||
* 수계별 유입ㆍ방류수질 분석
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
"use client";
|
||||
/**
|
||||
* 물재이용 차트
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useStore } from "zustand";
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { Button } from "@common/components";
|
||||
import type { ReuseStatusContent } from "../model/fo-interface";
|
||||
import { OperationApi, reuseStore } from "../api/fo-service";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
@@ -31,14 +35,29 @@ ChartJS.register(
|
||||
|
||||
|
||||
|
||||
const ReuseChart: React.FC = () => {
|
||||
// 임시 데이터 (이미지 기반 추정치)
|
||||
const data = {
|
||||
labels: ['2021', '2022', '2023', '2024'],
|
||||
const ReuseChart: React.FC = () => {
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const reuseStatusData = useStore(
|
||||
reuseStore,
|
||||
(state) => state.reuseStatus) as ReuseStatusContent[];
|
||||
|
||||
useEffect(() => {
|
||||
OperationApi.getReuseStatus().catch((e) => {
|
||||
console.error("물재이용 차트 데이터 조회 실패", e);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const labels = reuseStatusData?.map((r) => r.yr);
|
||||
const sewageData = reuseStatusData?.map((r) => r.sewage);
|
||||
const reclaimedData = reuseStatusData?.map((r) => r.reclaimed);
|
||||
const rainwaterData = reuseStatusData?.map((r) => r.rainwater);
|
||||
|
||||
const commonChartData = {
|
||||
datasets: [
|
||||
{
|
||||
label: '재이용량',
|
||||
data: [1125000, 1138000, 1135000, 1168000], // 이미지의 대략적인 값
|
||||
data: sewageData, // 기본은 하수처리수
|
||||
borderColor: '#00D4FF', // 선 색상 (밝은 하늘색)
|
||||
backgroundColor: 'rgba(0, 212, 255, 0.1)', // 채우기 색상 (투명도 있는 하늘색)
|
||||
borderWidth: 3,
|
||||
@@ -52,9 +71,8 @@ const ReuseChart: React.FC = () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
|
||||
const commonChartOptions: ChartOptions<'line'> = {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
@@ -104,17 +122,88 @@ const ReuseChart: React.FC = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const chartItems = [
|
||||
{
|
||||
title: "하수처리수 재이용량 (m³)",
|
||||
data: {
|
||||
...commonChartData,
|
||||
labels,
|
||||
datasets: [{ ...commonChartData.datasets[0], data: sewageData }],
|
||||
},
|
||||
options: commonChartOptions,
|
||||
},
|
||||
{
|
||||
title: "중수도 재이용량 (m³)",
|
||||
data: {
|
||||
...commonChartData,
|
||||
labels,
|
||||
datasets: [{ ...commonChartData.datasets[0], data: reclaimedData }],
|
||||
},
|
||||
options: commonChartOptions,
|
||||
},
|
||||
{
|
||||
title: "빗물 재이용량 (m³)",
|
||||
data: {
|
||||
...commonChartData,
|
||||
labels,
|
||||
datasets: [{ ...commonChartData.datasets[0], data: rainwaterData }],
|
||||
},
|
||||
options: commonChartOptions,
|
||||
},
|
||||
];
|
||||
|
||||
const startSlider = () => {
|
||||
intervalRef.current = setInterval(() => {
|
||||
setActiveIndex(prevIndex => (prevIndex + 1) % chartItems.length);
|
||||
}, 20000);
|
||||
};
|
||||
|
||||
const stopSlider = () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
startSlider();
|
||||
return () => stopSlider();
|
||||
}, []);
|
||||
|
||||
const handleIndicatorClick = (index: number) => {
|
||||
stopSlider();
|
||||
setActiveIndex(index);
|
||||
startSlider();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="card-header text-foreground">
|
||||
<h3 className="text-2xl font-medium">하수처리수 재이용량 (m³)</h3>
|
||||
<h3 className="text-2xl font-medium">{chartItems[activeIndex]?.title}</h3>
|
||||
<Button variant="outline" size="sm" className="text-white h-8 py-0 ml-auto">더보기</Button>
|
||||
</div>
|
||||
<div className="h-60 p-4">
|
||||
<div style={{ width: '100%', height: '100%' }}>
|
||||
<Line data={data} options={options} />
|
||||
<div className="h-60 p-4 relative overflow-hidden">
|
||||
<div
|
||||
className="flex transition-transform duration-500 ease-in-out h-full"
|
||||
style={{ transform: `translateX(-${activeIndex * 100}%)` }}
|
||||
>
|
||||
{chartItems.map((chart, index) => (
|
||||
<div key={index} className="w-full flex-shrink-0 h-full">
|
||||
<Line data={chart.data} options={chart.options as ChartOptions<'line'>} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex space-x-2">
|
||||
{chartItems.map((_, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => handleIndicatorClick(index)}
|
||||
className={`w-3 h-3 rounded-full cursor-pointer transition-colors ${
|
||||
activeIndex === index ? 'bg-blue-500' : 'bg-gray-300'
|
||||
}`}
|
||||
aria-label={`Go to slide ${index + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,44 +2,34 @@
|
||||
/**
|
||||
* 물재이용 현황
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useStore } from "zustand";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
interface ReuseData {
|
||||
year: string;
|
||||
reuseAmount: number;
|
||||
wasteWaterAmount: number;
|
||||
rainfallAmount: number;
|
||||
}
|
||||
import type { ReuseStatusContent } from "../model/fo-interface";
|
||||
import { OperationApi, reuseStore} from "../api/fo-service";
|
||||
|
||||
const ReuseStatus: React.FC = () => {
|
||||
// 임시 데이터
|
||||
const data: ReuseData[] = [
|
||||
{
|
||||
year: "'21",
|
||||
reuseAmount: 1126995,
|
||||
wasteWaterAmount: 254805510,
|
||||
rainfallAmount: 14049225,
|
||||
},
|
||||
{
|
||||
year: "'22",
|
||||
reuseAmount: 1136754,
|
||||
wasteWaterAmount: 243457789,
|
||||
rainfallAmount: 15128093,
|
||||
},
|
||||
{
|
||||
year: "'23",
|
||||
reuseAmount: 1137539,
|
||||
wasteWaterAmount: 222475997,
|
||||
rainfallAmount: 13944733,
|
||||
},
|
||||
{
|
||||
year: "'24",
|
||||
reuseAmount: 1168627,
|
||||
wasteWaterAmount: 265625918,
|
||||
rainfallAmount: 13305310,
|
||||
},
|
||||
];
|
||||
// const [reuseStatusData, setReuseStatusData] = useState<ReuseStatusContent[]>([]);
|
||||
const reuseStatusData = useStore(
|
||||
reuseStore,
|
||||
(state) => state.reuseStatus ) as ReuseStatusContent[];
|
||||
|
||||
useEffect(() => {
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// const data = await OperationApi.getReuseStatus();
|
||||
// setReuseStatusData(data || []); // API 응답이 null/undefined일 경우 빈 배열로 설정
|
||||
// } catch (error) {
|
||||
// console.error('물재이용 현황 데이터 조회에 실패했습니다.', error);
|
||||
// }
|
||||
// };
|
||||
// fetchData();
|
||||
// 여러 컴포넌트에서 호출해도 실제 API는 1번만
|
||||
OperationApi.getReuseStatus().catch((e) => {
|
||||
console.error("물재이용 현황 데이터 조회 실패", e);
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
// 숫자 포맷 함수 (천단위 쉼표)
|
||||
const formatNumber = (num: number): string => {
|
||||
@@ -72,19 +62,19 @@ const ReuseStatus: React.FC = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, index) => (
|
||||
{reuseStatusData && reuseStatusData.length > 0 && reuseStatusData?.map((row, index) => (
|
||||
<tr key={index} className="hover:bg-gray-50">
|
||||
<td className="bg-blue-100 font-semibold text-center py-1 px-3 border border-gray-300">
|
||||
{row.year}
|
||||
{row.yr}
|
||||
</td>
|
||||
<td className="text-center py-1 px-3 border border-gray-300 bg-white">
|
||||
{formatNumber(row.reuseAmount)}
|
||||
{formatNumber(row.sewage)}
|
||||
</td>
|
||||
<td className="text-center py-1 px-3 border border-gray-300 bg-white">
|
||||
{formatNumber(row.wasteWaterAmount)}
|
||||
{formatNumber(row.reclaimed)}
|
||||
</td>
|
||||
<td className="text-center py-1 px-3 border border-gray-300 bg-white">
|
||||
{formatNumber(row.rainfallAmount)}
|
||||
{formatNumber(row.rainwater)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 오수관로 및 우수관로 현황
|
||||
*/
|
||||
|
||||
@@ -73,6 +73,9 @@ export const ENDPOINT = {
|
||||
PPL_COUNTS: "/sewerage-service/api/v1/ms/pplCounts",
|
||||
OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts",
|
||||
},
|
||||
OPERATION_SERVICE: {
|
||||
REUSE_STATUS: "/operation-service/api/v1/dashBoard/main/reuse/status",
|
||||
},
|
||||
IC_SERVICE: {
|
||||
GROUPING_STATISTICS: "/ic-service/api/v1/nti/grouping-statistics",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user