Merge branch 'iipbs/sn/dev'

This commit is contained in:
ljpark
2025-10-27 09:47:07 +09:00
15 changed files with 470 additions and 131 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:80
NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
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_REUSE_API_URL= http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_WR_API_URL= http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service
NEXT_PUBLIC_NGII_URL = http://172.29.143.87
NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x}
@@ -10,8 +10,8 @@
*/
export default function CalcRainEffect(data : any, weather='ALL') {
console.log("%c Calculate RainEffect !" , "background:blue;color:white");
console.log("%c Data Length :"+data.length+" Weather : "+weather , "background:yellow;color:blue");
/* console.log("%c Calculate RainEffect !" , "background:blue;color:white");
console.log("%c Data Length :"+data.length+" Weather : "+weather , "background:yellow;color:blue");*/
// 강우영향일 계산 (강우량이 3 초과이면 무조건 강우영향일)
for(let idx = 0; idx < data.length; idx++) {
const item = data[idx];
@@ -7,13 +7,13 @@
"use client"
import { useFoStore } from "@/app/store";
import type { TableUpperProps } from "@/app/store/common";
import { type TableUpperProps } from "@/app/store/common";
import type { SewageOccurProps } from "@/app/store/sewageOccur";
import { ContentsDiv, downloadExcel, MainDiv, SearchBaseForm, SearchInput, SelectBox, Title, UiModal, UiTable, type ReturnType } from "@common/business_components";
import ListDiv from "@common/business_components/ui/list_div";
import LoadingOverlay from "@common/business_components/ui/loadingBar";
import { useCallback, useEffect, useRef, useState } from "react";
import SewageOccurModal from "./modal/page";
import type { SewageOccurProps } from "@/app/store/sewageOccur";
import LoadingOverlay from "@common/business_components/ui/loadingBar";
export default function PlanSewage() {
const {Common
@@ -21,6 +21,7 @@ export default function PlanSewage() {
,SewageOccurSearch
,initializeCommonData
,searchSewageOccurList
,resetExcelList
} = useFoStore((state) => state) || {
Common: { isInit: false, selectUpdownData: [], selectOperationData: [], selectSidoData: [], selectSigunData: [], selectRegionData: [], selectUnitWaterShedData: [], selectMiddleRegionData: [], selectEnvPartData: [], selectSearchYear: [{ val: "0" }], btnClass: "" },
SewageOccur: { sewageOccurList: [], sewageOccurCount: 0, excelList: [], upHeadList1: [], upHeadList2: [], upHeadList3: [] },
@@ -135,6 +136,7 @@ export default function PlanSewage() {
* useEffect
*/
useEffect(() => {
resetExcelList();
if (!isInit) {
initializeCommonData();
searchSewageOccurList(1, 10, selectSearchYear[0]?.val || "0");
@@ -142,15 +144,19 @@ export default function PlanSewage() {
}, [isInit, initializeCommonData, searchSewageOccurList]);
useEffect(() => {
if (excelList && excelList.length > 0) {
downloadExcel({
data: excelList as unknown as JSON[],
fileName: '오수발생량 추정',
sheetNameArray: ['오수발생량 추정'],
headerRange: 'A1:O1'
});
}
}, [excelList]);
const timer = setTimeout(() => {
if (excelList && excelList.length > 0) {
downloadExcel({
data: excelList as unknown as JSON[],
fileName: '오수발생량 추정',
sheetNameArray: ['오수발생량 추정'],
headerRange: 'A1:O1'
});
}
}, 500); // 약간의 지연을 두고 다운로드 실행
return () => clearTimeout(timer);
}, [excelList, isInit]);
/**
* 콜백함수
@@ -188,8 +194,12 @@ export default function PlanSewage() {
// 엑셀다운로드
const excelDownload = useCallback(() => {
searchFacilityPrecipitation(ref.current, 1, 10000);
}, [searchFacilityPrecipitation]);
searchFacilityPrecipitation(ref.current, 1, sewageOccurCount);
setTimeout(() => {
resetExcelList()
}, 1000); // 약간의 지연을 두고 초기화 (다운로드 완료 후)
}, [searchFacilityPrecipitation, sewageOccurCount]);
// 페이지 변경
const handlePageChange = useCallback((page: number) => {
@@ -250,4 +260,4 @@ export default function PlanSewage() {
)}
</MainDiv>
);
}
}
@@ -22,6 +22,7 @@ export default function UnderWaterInput() {
,UnderWaterSearch
,initializeCommonData
,searchUnderWaterList
,resetExcelList
} = useFoStore((state) => state) || {
Common: { isInit: false, selectUpdownData: [], selectOperationData: [], selectSidoData: [], selectSigunData: [], selectRegionData: [], selectUnitWaterShedData: [], selectMiddleRegionData: [], selectEnvPartData: [], selectSearchYear: [{ val: "0" }], btnClass: "" },
UnderWater: { underWaterList: [], underWaterCount: 0, excelList: [], upHeadList1: [], upHeadList2: [], upHeadList3: [] },
@@ -134,21 +135,25 @@ export default function UnderWaterInput() {
* useEffect
*/
useEffect(() => {
resetExcelList();
if (!isInit) {
initializeCommonData();
searchUnderWaterList(1, 10, selectSearchYear[0]?.val || "0");
}
}, [isInit, initializeCommonData, searchUnderWaterList, selectSearchYear]);
}, [isInit, initializeCommonData, searchUnderWaterList, selectSearchYear, resetExcelList]);
useEffect(() => {
if (excelList && excelList.length > 0) {
downloadExcel({
data: excelList as unknown as JSON[],
fileName: '지하수유입량 추정',
sheetNameArray: ['지하수유입량 추정'],
headerRange: 'A1:V1'
});
}
const timer = setTimeout(() => {
if (excelList && excelList.length > 0) {
downloadExcel({
data: excelList as unknown as JSON[],
fileName: '지하수유입량 추정',
sheetNameArray: ['지하수유입량 추정'],
headerRange: 'A1:V1'
});
}
}, 500); // 약간의 지연을 두고 다운로드 실행
return () => clearTimeout(timer);
}, [excelList]);
/**
@@ -187,8 +192,12 @@ export default function UnderWaterInput() {
// 엑셀다운로드
const excelDownload = useCallback(() => {
searchFacilityPrecipitation(ref.current, 1, 10000);
}, [searchFacilityPrecipitation]);
searchFacilityPrecipitation(ref.current, 1, underWaterCount);
setTimeout(() => {
resetExcelList();
}, 1000); // 약간의 지연을 두고 다운로드 실행
}, [resetExcelList, searchFacilityPrecipitation, underWaterCount]);
// 페이지 변경
const handlePageChange = useCallback((page: number) => {
@@ -252,6 +252,7 @@ export const facilityPrecipitationReducer:(set:(fn: (state: FacilityPrecipitatio
const excelDownList: ExcelType[] = [];
if(pageSize>5000){
console.log('엑셀다운로드 데이터 생성 : ', data.length);
data.forEach((item: FacilityPrecipitationProps) => {
excelDownList.push({
시설코드: item.bscFctCd,
+21 -1
View File
@@ -1,5 +1,5 @@
import { getFetch } from "@/app/components/api";
import DataForm from "@/app/components/common/DataForm";
import DataForm, { roundNumber } from "@/app/components/common/DataForm";
import type { TableUpperProps } from "../dashboard";
export type { TableUpperProps };
@@ -412,6 +412,26 @@ const gridListFcltyWaterQltyInfo:fcltyWaterQltyInfoProps[] = [
====================================*/
// 시설정보 리스트 데이터 호출(구분 선택 후 시설명 있을 경우)
export const fetchFacilityListInit = async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string ): Promise<{data:facilityListProps[], count:number}> => {
try {
const tailStr = partValue ? '/'+ (partValue==='G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : '';
const url = '/prcsFclt/info/'+pageNo+'/'+pageSize+tailStr;
const res = await getFetch('fo', url);
res.data.forEach((item: Record<string, unknown>) => {
item.daddr = item.addr ? item.addr : '';
item.sidoNm = item.sido ? item.sido : '';
item.sggNm = item.gugun ? item.gugun : '';
item.fctCpc = roundNumber(item.fctCpc as number, 0);
});
return res;
} catch (error) {
console.error('시설정보 데이터를 가져오는데 실패했습니다:', error);
return {data: [], count: 0};
}
};
const fetchFacilityList = async (pageNo:number, pageSize:number, partValue?:string, nameCd?:string, fcltCpct?:string, oprtngSttsCd?:string, facilityPartValue?:string, facilityName?:string ): Promise<{data:facilityListProps[], count:number}> => {
try {
const tailStr = partValue ? '/'+ (partValue==='G' ? 'A' : partValue) + '/' + nameCd + '/' + fcltCpct + '/' + oprtngSttsCd + '/' + facilityPartValue + (facilityName && facilityName !== '' ? '/' + facilityName : '') : '';
+177 -48
View File
@@ -1,8 +1,9 @@
import DataForm from "@/app/components/common/DataForm";
import type { TableUpperProps } from "../dashboard";
import { getFetch } from "@/app/components/api";
import CalcRainEffect from "@/app/components/common/CalcRainEffect";
import { roundNumber } from "@/app/components/common/DataForm";
import { OSU_PERCENT } from "../common";
import type { TableUpperProps } from "../dashboard";
import { fetchFacilityListInit } from "../processFacility";
export type { TableUpperProps };
/**====================================
* Store - 타입
@@ -124,24 +125,30 @@ const tempSewageOccurList:modalTableType[] = []
* Store - API
====================================*/
const fetchSewageOccurList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
/*const fetchSewageOccurList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
// 20250701임시시
const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : '';
const url = '/fclt/flow/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr;
const res = await getFetch('fo', url);
return { data: res.data, count: res.count }
/*
const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : '';
const url = '/fclt/qlty/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr;
const res = await getFetch('fo', url);
return { data: res.data, count: res.count } */
return { data: res.data, count: res.count }
}*/
const fetchSweageOccurListFlow = async(fcltyCd : string, searchYear : string) => {
const url = '/fclt/flow/' + fcltyCd + '/' + searchYear;
const res = await getFetch('fo', url);
return res;
}
/**====================================
* Store - expaort
====================================*/
export type SewageOccurActions = {
resetExcelList: () => void;
searchSewageOccurList: (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => void;
searchSewageOccurDetailList: (bscFctCd: string, searchYear: string, startYearMonth?: string, endYearMonth?: string) => void;
}
@@ -194,6 +201,17 @@ export const sewageOccurInitState: SewageOccurType = {
}
}
// 병렬 처리 제한용 헬퍼
async function processInBatches(items, batchSize, callback) {
const results:any = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(callback));
results.push(...batchResults);
}
return results;
}
export type SewageOccurStore = SewageOccurType & SewageOccurActions;
// Add type definitions for API responses
@@ -212,33 +230,58 @@ export type FlowResponse = {
export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => {
return {
searchSewageOccurList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
resetExcelList: () => {
set((state: SewageOccurType) => ({
...state,
SewageOccur: {
...state.SewageOccur,
isLoading: true,
},
...state,
SewageOccur: {
...state.SewageOccur,
excelList: [],
},
}));
const res = await fetchSewageOccurList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm);
const excelDownList: ExcelType[] = [];
const data = res.data;
// Promise.all을 사용하여 모든 비동기 작업이 완료될 때까지 기다림
const calData = await Promise.all(data.map(async (item: SewageOccurProps) => {
item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || '';
item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || '';
},
searchSewageOccurList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
const facilityInitData = await fetchFacilityListInit(pageNo, pageSize, part, nameCd, fcltCpct, operationValue, fcltyNm);
if(pageSize < facilityInitData.count){
set((state: SewageOccurType) => ({
...state,
SewageOccur: {
...state.SewageOccur,
sewageOccurList : facilityInitData.data,
sewageOccurCount : facilityInitData.count,
excelList : [],
isLoading: true
},
}));
}else{
set((state: SewageOccurType) => ({
...state,
SewageOccur: {
...state.SewageOccur,
excelList : [],
isLoading: true
},
}));
}
const fcltylist = facilityInitData.data;
const fcltyCount = facilityInitData.count;
const calData = await processInBatches(fcltylist, 20, async (item: SewageOccurProps, index: number) => {
const bscFctCd = item.bscFctCd;
const res = await fetchSweageOccurListFlow(bscFctCd, searchYear);
item.sidoNm = item.sidoNm || '';
item.sggNm = item.sggNm || '';
item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, ''));
item.swageChangePercent = 90;
const bscFctCd = item.bscFctCd;
const res2 = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear) as FlowResponse;
let sumChungcunDay = 0;
let chungcunSumFlow = 0;
let totWaterUsage = 0;
// 강우영향일 계산 (강우량이 3 초과이면 무조건 강우영향일)
const rainEffectData = CalcRainEffect(res2.data);
const rainEffectData = CalcRainEffect(res.data);
rainEffectData.forEach((item: FlowDataItem) => {
@@ -246,27 +289,25 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => {
chungcunSumFlow += Number(item.inflAmnt);
sumChungcunDay += 1;
}
totWaterUsage += Number(item.inflAmnt);
});
const avg = totWaterUsage /res2.count;
const avg = res.count == 0 ? 0 : totWaterUsage /res.count;
item.waterUsage = parseFloat((avg * 0.65).toFixed(2));
item.sewageInputYear = parseFloat(avg.toFixed(2));
item.sewageInputChungchun = parseFloat((chungcunSumFlow===0?0:(chungcunSumFlow / sumChungcunDay)).toFixed(2));
item.estimationSewage = parseFloat((item.waterUsage * (item.swageChangePercent/100)).toFixed(2));
return item;
}));
calData.forEach((item: SewageOccurProps) => {
DataForm({ type: 'float0', column: 'fctCpc', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'waterUsage', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'swageChangePercent', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'estimationSewage', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'sewageInputYear', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'sewageInputChungchun', item: item as unknown as Record<string, unknown> });
// 반올림 처리
["fctCpc", "waterUsage", "swageChangePercent", "estimationSewage", "sewageInputYear", "sewageInputChungchun"].forEach(
(key) => (item[key] = roundNumber(item[key], 0))
);
return {...item, index:index};
});
if(pageSize>5000){
if(pageSize >= fcltyCount){
const excelDownList: ExcelType[] = [];
calData.forEach((item: SewageOccurProps) => {
excelDownList.push({
시설코드: item.bscFctCd,
@@ -286,21 +327,109 @@ export const sewageOccurReducer:(set:any)=>SewageOccurActions=(set: any) => {
"청천시하수유입량(m³/일)": item.sewageInputChungchun,
});
});
set((state: SewageOccurType) => ({
...state,
SewageOccur: {
...state.SewageOccur,
excelList: excelDownList,
isLoading : false,
},
}));
}else{
set((state: SewageOccurType) => ({
...state,
SewageOccur: {
...state.SewageOccur,
sewageOccurList : calData,
sewageOccurCount : facilityInitData.count,
isLoading: false,
excelList: [],
},
}));
}
// 조회 데이터 설정
set((state: any) => ({
...state,
SewageOccur: pageSize<5000?{
...state.SewageOccur,
sewageOccurList: calData,
sewageOccurCount: res.count,
isLoading : false,
}:{
...state.SewageOccur,
excelList: excelDownList,
isLoading : false,
},
}))
/* fetchSewageOccurList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm).then(async (res) => {
const excelDownList: ExcelType[] = [];
const data = res.data;
// Promise.all을 사용하여 모든 비동기 작업이 완료될 때까지 기다림
const calData = await Promise.all(data.map(async (item: SewageOccurProps) => {
item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || '';
item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || '';
item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, ''));
item.swageChangePercent = 90;
const bscFctCd = item.bscFctCd;
const res2 = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear) as FlowResponse;
let sumChungcunDay = 0;
let chungcunSumFlow = 0;
let totWaterUsage = 0;
// 강우영향일 계산 (강우량이 3 초과이면 무조건 강우영향일)
const rainEffectData = CalcRainEffect(res2.data);
rainEffectData.forEach((item: FlowDataItem) => {
if(Number(item.rainEffectDay) === 0){
chungcunSumFlow += Number(item.inflAmnt);
sumChungcunDay += 1;
}
totWaterUsage += Number(item.inflAmnt);
});
const avg = totWaterUsage /res2.count;
item.waterUsage = parseFloat((avg * 0.65).toFixed(2));
item.sewageInputYear = parseFloat(avg.toFixed(2));
item.sewageInputChungchun = parseFloat((chungcunSumFlow===0?0:(chungcunSumFlow / sumChungcunDay)).toFixed(2));
item.estimationSewage = parseFloat((item.waterUsage * (item.swageChangePercent/100)).toFixed(2));
return item;
}));
calData.forEach((item: SewageOccurProps) => {
DataForm({ type: 'float0', column: 'fctCpc', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'waterUsage', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'swageChangePercent', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'estimationSewage', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'sewageInputYear', item: item as unknown as Record<string, unknown> });
DataForm({ type: 'float0', column: 'sewageInputChungchun', item: item as unknown as Record<string, unknown> });
});
if(pageSize>5000){
calData.forEach((item: SewageOccurProps) => {
excelDownList.push({
시설코드: item.bscFctCd,
권역: item.rvbsn,
단위유역: item.lgbsn,
중권역: item.mdmbsn,
환경청: item.rgnNm,
시설명: item.bscFctNm,
시도: item.sidoNm,
시군구: item.sggNm,
주소: item.daddr,
"시설용량(m³/일)": item.fctCpc,
"물사용량(m³/일)": item.waterUsage,
"오수전환율(%)": item.swageChangePercent,
"오수발생량추정(m³/일)": item.estimationSewage,
"연간하수유입량(m³/일)": item.sewageInputYear,
"청천시하수유입량(m³/일)": item.sewageInputChungchun,
});
});
}
// 조회 데이터 설정
set((state: any) => ({
...state,
SewageOccur: pageSize<5000?{
...state.SewageOccur,
sewageOccurList: calData,
sewageOccurCount: res.count,
isLoading : false,
}:{
...state.SewageOccur,
excelList: excelDownList,
isLoading : false,
},
}))
}); */
},
searchSewageOccurDetailList: async (bscFctCd: string, searchYear: string, startYearMonth?: string, endYearMonth?: string) => {
+98 -46
View File
@@ -1,9 +1,10 @@
import { getFetch } from "@/app/components/api";
import DataForm from "@/app/components/common/DataForm";
import DataForm, { roundNumber } from "@/app/components/common/DataForm";
import type { TableUpperProps } from "../dashboard";
import type { ExcelType, UnderWaterInputModalProps, UnderWaterProps } from "./types";
import CalcRainEffect from "@/app/components/common/CalcRainEffect";
import { OSU_PERCENT } from "../common";
import { fetchFacilityListInit } from "../processFacility";
export type { TableUpperProps };
// SewageOccurDetailProps 타입을 여기에 정의
@@ -187,24 +188,32 @@ export interface ApiResponse {
count: number;
}
/*
const fetchUnderWaterList = async (pageNo: number, pageSize: number, searchYear: string, partCd?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : '';
const url = '/fclt/flow/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr;
const res = await getFetch('fo', url);
return { data: res.data, count: res.count }
/*
const tailStr = partCd ? '/'+ partCd + '/' + nameCd + '/' + fcltCpct + '/' + operationValue + (fcltyNm && fcltyNm !== '' ? '/' + fcltyNm : '') : '';
const url = '/fclt/qlty/' + pageNo + '/' + pageSize + '/' + searchYear + tailStr;
const res = await getFetch('fo', url);
return { data: res.data, count: res.count } */
return { data: res.data, count: res.count }
//return { data: underWaterList, count: 2 };
}*/
const fetchUnderWaterListFlow = async(fcltyCd : string, searchYear : string) => {
const url = '/fclt/flow/' + fcltyCd + '/' + searchYear;
const res = await getFetch('fo', url);
return res;
}
/**====================================
* Store - expaort
====================================*/
export type UnderWaterActions = {
resetExcelList: () => void;
searchUnderWaterList: (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => void;
unserWaterModal:(bscFctCd:string, searchYear:string, startYm:string, endYm:string)=>void
}
@@ -220,7 +229,7 @@ export type UnderWaterType = {
upHeadList1: TableUpperProps[];
upHeadList2: TableUpperProps[];
upHeadList3: TableUpperProps[];
underWaterList: UnderWaterProps[]; // Changed from facilityAreaProps to ProcessAreaProps
underWaterList: any[]; // Changed from facilityAreaProps to ProcessAreaProps
underWaterCount: number;
excelList: ExcelType[];
isLoading: boolean;
@@ -265,29 +274,65 @@ const dataFormSetting=(item : UnderWaterProps)=> {
DataForm({ type: 'float0', column: 'underWaterInputWinter', item: item as unknown as Record<string, unknown>});
}
// 병렬 처리 제한용 헬퍼
async function processInBatches(items, batchSize, callback) {
const results :any = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(callback));
results.push(...batchResults);
}
return results;
}
export type UnderWaterStore = UnderWaterType & UnderWaterActions;
export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterType) => void)=>UnderWaterActions=(set: (fn: (state: UnderWaterType) => UnderWaterType) => void) => {
return {
searchUnderWaterList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
resetExcelList: () => {
set((state: UnderWaterType) => ({
...state,
UnderWater: {
...state.UnderWater,
isLoading: true,
excelList : [],
},
}));
const res = await fetchUnderWaterList(pageNo, pageSize, searchYear, part, nameCd, fcltCpct, operationValue, fcltyNm);
const data = res.data;
const calData = await Promise.all(data.map(async (item: UnderWaterProps) => {
item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || '';
item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || '';
item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, ''));
},
searchUnderWaterList: async (pageNo: number, pageSize: number, searchYear: string, part?: string, nameCd?: string, fcltCpct?: string, operationValue?: string, fcltyNm?: string) => {
const facilityInitData = await fetchFacilityListInit(pageNo, pageSize, part, nameCd, fcltCpct, operationValue, fcltyNm);
if(pageSize < facilityInitData.count){
set((state: UnderWaterType) => ({
...state,
UnderWater: {
...state.UnderWater,
underWaterList : facilityInitData.data,
underWaterCount : facilityInitData.count,
isInit : true,
isLoading: true,
},
}));
}else{
set((state: UnderWaterType) => ({
...state,
UnderWater: {
...state.UnderWater,
isLoading: true,
},
}));
}
const fcltylist = facilityInitData.data;
const fcltyCount = facilityInitData.count;
const calData = await processInBatches(fcltylist, 20, async (item: any, index: number) => {
const bscFctCd = item.bscFctCd;
const res2 = await getFetch('fo', '/fclt/flow/' + bscFctCd + '/' + searchYear) as ApiResponse;
const calData = CalcRainEffect(res2.data, 'SUNNY');
const res = await fetchUnderWaterListFlow(bscFctCd, searchYear);
item.sidoNm = item.sido || '';
item.sggNm = item.gugun || '';
item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, ''));
let chungcunSumFlow = 0;
@@ -297,7 +342,8 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy
let sumWinterChungcunDay = 0;
let sumWinterChungcunFlow = 0;
calData.forEach((flowItem: FlowDataItem) => {
const rainEffectData = CalcRainEffect(res.data, 'SUNNY');
rainEffectData.forEach((flowItem: FlowDataItem) => {
chungcunSumFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0;
if(flowItem.msrDt.substring(4,6) ==='06' || flowItem.msrDt.substring(4,6) ==='07' || flowItem.msrDt.substring(4,6) ==='08'){
@@ -308,25 +354,32 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy
sumWinterChungcunFlow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0;
}
});
const usage = chungcunSumFlow&&chungcunSumFlow>0?chungcunSumFlow / calData.length:0;
let totWaterInflow = 0
const rainEffectTot = CalcRainEffect(res.data);
rainEffectTot.forEach((flowItem: FlowDataItem) => {
totWaterInflow += flowItem.inflAmnt && flowItem.inflAmnt !== 'x' ? Number(flowItem.inflAmnt.toString().trim()) : 0;
});
const avg = res.count === 0 ? 0 : totWaterInflow / res.count;
const usage = chungcunSumFlow&&chungcunSumFlow>0?chungcunSumFlow / rainEffectData.length:0;
const summerUsage = sumSummerChungcunFlow?sumSummerChungcunFlow / sumSummerChungcunDay:0;
const winterUsage = sumWinterChungcunFlow?sumWinterChungcunFlow / sumWinterChungcunDay:0;
//bscFctCd === '11200PB001R') 중랑 - 테스트할 때 코드넣고 해야 보기편해요~
/* if(bscFctCd === '11200PB001R') {
console.log('calData.length : ', calData.length);
console.log('usage : ', usage, 'summerUsage : ', summerUsage, 'winterUsage : ', winterUsage);
} */
item.inflAmnt = roundNumber(avg, 0);
item.underWaterInputYear = Math.round((usage-(usage*0.65*OSU_PERCENT)) * 1) / 1;
item.underWaterInputSummer = Math.round((summerUsage-(summerUsage*0.65*OSU_PERCENT)) * 1) / 1;
item.underWaterInputWinter = Math.round((winterUsage-(winterUsage*0.65*OSU_PERCENT)) * 1) / 1;
item.properYn = Math.round( (Number(usage-(usage*0.65*OSU_PERCENT)) / Number(item.inflAmnt)) * 100 * 1 ) / 1 > 20?'초과유입':'적정';
return item;
}));
return {...item, index:index};
});
const excelDownList: ExcelType[] = [];
if(pageSize>5000){
calData.forEach(item => {
if(pageSize>= fcltyCount){
const excelDownList: ExcelType[] = [];
calData.forEach((item : any) => {
dataFormSetting(item)
item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, ''));
/* item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || '';
@@ -349,27 +402,26 @@ export const underWaterReducer:(set:(fn: (state: UnderWaterType) => UnderWaterTy
적정여부: item.properYn,
});
});
set((state: UnderWaterType) => ({
...state,
UnderWater: {
...state.UnderWater,
excelList: excelDownList,
isLoading : false,
},
}));
}else{
data.forEach((item: UnderWaterProps) => {
dataFormSetting(item)
item.fctCpc = parseFloat(item.fctCpc.toString().replace(/,/g, ''));
/* item.sidoNm = item.lclgvrnmntNm.split(' ')[0]?.toString() || '';
item.sggNm = item.lclgvrnmntNm.split(' ')[1]?.toString() || ''; */
});
set((state: UnderWaterType) => ({
...state,
UnderWater: {
...state.UnderWater,
underWaterList: calData,
underWaterCount: facilityInitData.count,
excelList: [],
isLoading : false,
},
}));
}
set((state: UnderWaterType) => ({
...state,
UnderWater: pageSize<5000?{
...state.UnderWater,
underWaterList: calData,
underWaterCount: res.count,
isLoading : false,
}:{
...state.UnderWater,
isLoading : false,
excelList: excelDownList
},
}))
},
unserWaterModal:async (bscFctCd, searchYear, startYm, endYm)=>{
+14 -3
View File
@@ -33,9 +33,20 @@ function buildAuthHeaders(req: NextRequest): Headers {
/** 공통: rewrite 응답 생성 (헤더 주입 여부 선택) */
function rewriteWith(url: string, req: NextRequest, withAuth = false): NextResponse {
return withAuth
? NextResponse.rewrite(new URL(url), { headers: buildAuthHeaders(req) })
: NextResponse.rewrite(new URL(url));
const headers = withAuth ? buildAuthHeaders(req) : new Headers(req.headers);
if (url.indexOf("/operation-service") !== -1) {
// x-forwarded- 로 시작하는 헤더의 첫 번째 값만 남김
for (const [key, value] of headers.entries()) {
if (key.startsWith('x-forwarded-') && value.includes(',')) {
const first = (value?.split(',')[0] || '').trim();
headers.set(key, first);
}
}
}
return NextResponse.rewrite(new URL(url), { headers });
}
export function middleware(req: NextRequest) {
+1
View File
@@ -12,6 +12,7 @@ NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
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_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1
#NEXT_PUBLIC_WR_SERVER_API_URL = http://192.168.1.222:8883/reuse-service/api/v1
NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y}
+13
View File
@@ -0,0 +1,13 @@
"use client";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>{children}</>
);
}
@@ -0,0 +1,65 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
export default function HasuStatPage() {
const [status, setStatus] = useState<string>("checking");
useEffect(() => {
let didCancel = false;
async function checkAndRedirect() {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3000);
const res = await fetch(`/api/client-ip`, { signal: controller.signal });
clearTimeout(timeout);
if (!res.ok) throw new Error("no-ip");
const data = await res.json();
const ip: string = data?.ip || "";
if (didCancel) return;
alert("Client IP:" + ip);
const is10 = /^10\./.test(ip);
const is172 = /^172\./.test(ip);
setStatus(is10||is172 ? "redirect-internal" : "redirect-root");
// Use location.replace so user can't go back to this page
if (is10||is172) {
// redirect to internal IP
window.open('http://10.101.163.102:8888/cms/lay1/WS10000014/program.do', '_blank');
} else {
window.open('https://www.hasudoinfo.or.kr/cms/lay1/WS10000014/program.do', '_blank');
}
window.history.back();
} catch {
// If anything fails, fall back to root
if (!didCancel) {
setStatus("error-fallback");
window.location.replace("/");
}
}
}
checkAndRedirect();
return () => {
didCancel = true;
};
}, []);
return (
<div style={{padding:20,fontFamily:'Noto Sans KR, sans-serif'}}>
{status === "checking" && <div>접속 IP 확인 중...</div>}
{status === "redirect-internal" && <div>내부망으로 이동합니다...</div>}
{status === "redirect-root" && <div>외부에서 접속하여 루트로 이동합니다...</div>}
{status === "error-fallback" && (
<div>
문제가 발생하여 루트로 이동합니다. 이동하지 않으면 <Link href="/">여기</Link>를 클릭하세요.
</div>
)}
</div>
);
}
+28
View File
@@ -0,0 +1,28 @@
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const headers = request.headers;
// 1. x-forwarded-for 또는 x-real-ip에서 IP 추출
let ip = headers.get('x-forwarded-for') || headers.get('x-real-ip') || '';
// IP가 있으면 첫 번째 IP 주소만 추출
if (ip) {
if (ip.includes(',')) {
ip = ip.split(',')[0]?.trim()||'';
} else {
ip = ip.trim();
}
}
// 3. IPv6 주소 정규화
if (ip.startsWith('::ffff:')) {
ip = ip.replace('::ffff:', '');
}
// 4. localhost IPv6를 IPv4로 변환
if (ip === '::1') {
ip = '127.0.0.1';
}
return NextResponse.json({ ip });
}
@@ -30,9 +30,9 @@ export const baseReuseBuildingSchema = z.object({
crgRdctAmt: z.number().default(0), // 안쓰는 값
ruseFcltFlg: z.string().default(""),
bdstUsgCd: z
.number()
.default(0)
.refine((val) => val === -1 || val >= 1, {
.string()
.default('000')
.refine((val) => val.trim().length > 0, {
message: validationMessages.requiredSelect("건축물용도"),
}),
lawTrgtYn: z.string().default("Y"),
@@ -82,7 +82,7 @@ export default function RainRegForm({
operMngCd: item?.operMngCd ?? "", // null → ""
grntchBdstCertYr:
typeof item?.grntchBdstCertYr === "string"
? item?.grntchBdstCertYr
? item?.grntchBdstCertYr.trim()
: "", // null → ""
bdstUsgCd: item?.bdstUsgCd ?? Number("-1"), // null → "-1"
instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y"