Merge branch 'iipbs/sn/dev'

This commit is contained in:
2025-07-25 20:43:15 +09:00
13 changed files with 266 additions and 207 deletions
@@ -28,10 +28,11 @@ export const MsSwagePplCol = [
// { id: 'lyrTypeNm', title: '레이어타입명' },
]
export const MsSwagePplCol2 = [
{ id: 'rowNumber', title: '일련번호' },
{ id: 'fcltMngNo', title: '시설 관리번호' },
{ id: 'instMngNo', title: '기관 관리번호' },
{ id: 'instlYmd', title: '설치일자' },
{ id: 'mapshtNo', title: '도엽번호' },
// { id: 'mapshtNo', title: '도엽번호' },
{ id: 'pdmt', title: '관경(mm)' },
{ id: 'prlg', title: '연장(m)' },
{ id: 'avgGrdn', title: '평균구배(m/m)' },
@@ -296,10 +297,11 @@ export const SwtrmtFcltCol = [
// { id: 'obsrvnSptCd', title: '관측지점코드' },
];
export const SwtrmtFcltCol2 = [
{ id: 'rowNumber', title: '일련번호' },
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
{ id: 'fcltCpct', title: '시설용량(㎥/d)' },
{ id: 'rgnExpln', title: '지역설명' },
{ id: 'cmcnYmd', title: '준공일자' },
// { id: 'cmcnYmd', title: '준공일자' },
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
// { id: 'clsgYmd', title: '폐쇄일자' },
// { id: 'prcsMsrcCd', title: '처리공법코드' },
@@ -319,7 +321,7 @@ export const SwtrmtFcltCol2 = [
// { id: 'pllnArea', title: '부지면적(m²)' },
// { id: 'mngRprtvBzentyNm', title: '관리대행업체명' },
{ id: 'swtrmtPpltnCnt', title: '하수처리인구수(인)' },
{ id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' },
// { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' },
// { id: 'xcrd', title: 'X좌표' },
// { id: 'ycrd', title: 'Y좌표' },
// { id: 'oprtngSttsCd', title: '가동상태코드' },
@@ -346,6 +348,7 @@ export const SwtrmtPrcsZoneCol = [
{ id: 'swtrmt_fclt_nm', title: '하수처리시설명' },
];
export const SwtrmtPrcsZoneCol2 = [
{ id: 'rowNumber', title: '일련번호' },
// { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
// { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' },
@@ -380,12 +383,13 @@ export const MsSwageMhlCol = [
// { id: 'inst_mng_no', title: '기관관리번호' }
];
export const MsSwageMhlCol2 = [
{ id: 'rowNumber', title: '일련번호' },
// { id: 'fid', title: 'FID' },
// { id: 'geom', title: 'geometry' },
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
{ id: 'fcltMngNo', title: '시설관리번호' },
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
{ id: 'mapshtNo', title: '도엽번호' },
// { id: 'mapshtNo', title: '도엽번호' },
// { id: 'mngInstCd', title: '관리기관코드' },
{ id: 'instlYmd', title: '설치일자' },
// { id: 'lastDrdgnYmd', title: '최종준설일자' },
@@ -487,12 +491,13 @@ export const ExclnSewagePumpCol = [
// { id: 'instMngNo', title: '기관관리번호' },
]
export const ExclnSewagePumpCol2 = [
{ id: 'rowNumber', title: '일련번호' },
// { id: 'fid', title: 'FID' },
// { id: 'geom', title: 'geometry' },
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
{ id: 'fcltMngNo', title: '시설관리번호' },
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
{ id: 'mapshtNo', title: '도엽번호' },
// { id: 'mapshtNo', title: '도엽번호' },
// { id: 'mngInstCd', title: '관리기관코드' },
{ id: 'instlYmd', title: '설치일자' },
{ id: 'swagePmstnNm', title: '하수펌프장명' },
@@ -528,6 +533,7 @@ export const DrtwSewagePumpCol = [
// { id: 'instMngNo', title: '기관관리번호' },
]
export const DrtwSewagePumpCol2 = [
{ id: 'rowNumber', title: '일련번호' },
// { id: 'fid', title: 'FID' },
// { id: 'geom', title: 'geometry' },
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
@@ -574,6 +580,7 @@ export const stofcbrCol = [
{ id: 'drtn_angl', title: '방향각도(°)' },
]
export const stofcbrCol2 = [
{ id: 'rowNumber', title: '일련번호' },
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
{ id: 'fcltMngNo', title: '시설관리번호' },
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
@@ -650,7 +657,8 @@ export const dateFields = [
// 🎯 camelCase 버전 추가
'instlYmd', 'lastDrdgnYmd', 'cmcnYmd', 'oprtngStrtYmd', 'clsgYmd',
'mesureBeginDt', 'mesureEndDt', 'qltwtrBeginDt', 'qltwtrEndDt',
'rainBeginDt', 'rainEndDt'
'rainBeginDt', 'rainEndDt',
'flfmtBgngYmd', 'flfmtEndYmd', 'flfmtYr'
];
// 🎯 연월 필드들 (yyyymm -> yyyy년 m월)
@@ -677,5 +685,6 @@ export const numberFields = [
'worflwWallHgt',
'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt',
'precdngDry', 'rainFall', 'cntncTime', 'rainIn',
'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc'
'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc',
'bizAmt', 'count'
];
@@ -1,7 +1,6 @@
import {useState, useEffect} from 'react';
import { getData, getDcData } from '@/components/lib/api';
import { getData } from '@/components/lib/api';
import moment from "moment";
import { library } from 'webpack';
export type TInftwtAnls = {
inftwtAnlsSn:number;
@@ -77,7 +76,7 @@ function roundTo(n: number, digits: number): number {
}
const getOptionsAplcnYm = (yyyymms: string[]): { text: string; val: string, hidden?: boolean }[] => {
const yyyymms1 = yyyymms.map(dateStr => {
const yyyymms1 : { text: string; val: string; hidden?: boolean }[] = yyyymms.map(dateStr => {
const year = dateStr.slice(0, 4);
const month = dateStr.slice(4, 6);
return {
@@ -115,7 +114,7 @@ export const useAplcnYms = (gubun:string) => {
return data;
};
export const getOptionsInftwtAnlsInfo = (anlsArr: TInftwtAnls[]): { text: string; val: number, hidden?: boolean }[] => {
const options = anlsArr.map(anls => ({
const options : { text: string; val: number; hidden?: boolean }[] = anlsArr.map(anls => ({
text: anls.anlsInfo,
val: anls.inftwtAnlsSn,
}));
@@ -214,7 +213,7 @@ export const selectPplRdiiQty = async(rainInfwtAnlsSn:number, pplMngNo:string) =
};
export const getOptionsRdiiAnlsInfo = (anlsArr: TRdiiAnls[]): { text: string; val: number, hidden?: boolean }[] => {
const options = anlsArr.map(anls => ({
const options : { text: string; val: number; hidden?: boolean }[] = anlsArr.map(anls => ({
text: anls.anlsInfo,
val: anls.rainInfwtAnlsSn,
}));
@@ -5,7 +5,7 @@ import { Button, Input, Label } from '@common/components';
import React, { useEffect, useState } from 'react';
import moment from "moment";
import type { TInftwtAnls, TOilqData } from './aplcnYms';
import type { TInftwtAnls } from './aplcnYms';
import * as fn from './aplcnYms';
import { useInfilChartModalStore } from '../chartModalStore'; // 모달 상태 관리 스토어
type ModalOpenProp = {
@@ -20,7 +20,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => {
const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치
const [selectedAplcnYm, setSelectedAplcnYm] = useState("");
const [inftwtAnlss, setInftwtAnlss] = useState<TInftwtAnls[]>([]);
const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<any[]>([{ val: 0, text: '선택' }]); // 침입수분석정보 options
const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 침입수분석정보 options
const [inftwtAnls, setInftwtAnls] = useState<TInftwtAnls | null>(null); // 침입수분석정보
const [selectedInftwtAnls, setSelectedInftwtAnls] = useState<number>(0); // 침입수분석 정보 선택시 사용
const [chartData, setChartData] = useState<{ chartData1: DataType[]; chartData2: DataType[] }>({
@@ -27,7 +27,7 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => {
const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치
const [aplcnYm, setAplcnYm] = useState("");
const [rdiiAnlss, setRdiiAnlss] = useState<TRdiiAnls[]>([]);
const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<any[]>([{ val: 0, text: '선택' }]); // 강우유입수분석정보 options
const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 강우유입수분석정보 options
const [rdiiAnls, setRdiiAnls] = useState<TRdiiAnls | null>(null); // 강우유입수분석정보
const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState<number>(0); // 강우유입수분석 정보 선택시 사용
const [chartData, setChartData] = useState<lineBarChartData[]>([]);
@@ -83,7 +83,6 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => {
setRdiiAnlsInfos([]);
return;
}
console.log(moment(aplcnYm).format("YYYY-MM"));
fn.selectRdiiAnls(aplcnYm)
.then((data) => {
setRdiiAnlss(data ?? []); // 선택한 월의 분석성공한 RDII분석데이터[]
@@ -106,7 +105,7 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => {
if (selectedAnls) {
setRdiiAnls(selectedAnls);
}
}, [rainInfwtAnlsSn]);
}, [rainInfwtAnlsSn, rdiiAnlss]);
const closePopup = () => { // 팝업 닫기
setPopupOpen(false);
@@ -136,7 +135,6 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => {
item.barVal0 = matchedVal;
}
});
console.log('rsltDataformatted :', rsltDataformatted);
setChartData(rsltDataformatted); // 차트 데이터 설정
}
const renderBody = () => (
@@ -2,14 +2,14 @@ import { VerticalTable } from '@/components/ui/verticalTable';
import React, { useState, useEffect } from 'react'
// 빗물펌프 자체 입력 정보
const exclnInputColumn = [
{ id: "swtrmtZoneCd", title: "배수구역", type: "input" },
{ id: "mltvDtdvNm", title: "배수분구", type: "input" },
{ id: "swtrmtZoneCd", title: "처리구역", type: "input" },
{ id: "mltvDtdvNm", title: "처리분구", type: "input" },
{ id: "dsgnFrqnc", title: "설계빈도(년)", type: "input" },
]
// 오수펌프 자체 입력 정보
const drtwInputColumn = [
{ id: "swtrmt_zone_cd", title: "배수구역", type: "input" },
{ id: "mltv_dtdv_nm", title: "배수분구", type: "input" },
{ id: "swtrmt_zone_cd", title: "처리구역", type: "input" },
{ id: "mltv_dtdv_nm", title: "처리분구", type: "input" },
]
// 하수펌프장
@@ -95,13 +95,13 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
else if (lyrTyNm === 'tb_ms_swage_pmstn') { // 하수펌프장
const drtwPumpCode = DRTW_SEWAGE_PUMP_USE_CODES.find(code => code.text === '오수펌프장');
if (drtwPumpCode && featureData.values_.usg_cd.trim() === drtwPumpCode.val) {
setIsDrtwSewagePump(false);
setHeadlist(ExclnSewagePumpCol);
}
else {
setIsDrtwSewagePump(true);
setHeadlist(DrtwSewagePumpCol);
}
else {
setIsDrtwSewagePump(false);
setHeadlist(ExclnSewagePumpCol);
}
setLayerSpecificNm(featureData.values_.fclt_mng_no);
result = '하수펌프장';
}
@@ -473,55 +473,58 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
return (
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[50]">
<LoadingOverlay isLoading={isLoading} />
<div className="card-header bg-primary flex items-center justify-between p-4">
<div className="flex items-center">
<h3 className="text-2xl font-medium text-white text-label whitespace-nowrap mr-4">{layerNm}
<div className="card-header bg-primary flex items-center justify-between relative p-4">
<div className="flex items-center space-x-4 flex-1">
<h3 className="text-2xl font-medium text-white text-label whitespace-nowrap">{layerNm}
{tableData?.usg_cd && (
<>
({tableData.usg_cd})
</>
)} 정보 <br />관리번호 : {layerSpecificNm}
</h3>
<div className="flex items-center space-x-3">
{/* 🎯 하수관로일 때만 Infil/RDII 버튼 표시 */}
{layerNm === '하수관로' && (
<>
<Button onClick={() => setIsModalInfil(true)}
className="py-1 text-xl text-blue-600 border border-blue-600 rounded hover:bg-gray-700"
color="white"
size="sm"
>
침입수분석
</Button>
<Button onClick={() => setIsModalRdii(true)}
className="py-1 text-xl text-gray-600 border border-gray-600 rounded hover:bg-gray-700"
color="white"
size="sm"
>
RDII분석
</Button>
</>
)}
{layerNm === '하수맨홀' && (
<>
<Button onClick={() => {
setIsLoading(true);
searchUpperPpls(layerSpecificNm).finally(() => setIsLoading(false));
}}
className="px-2 py-1 text-xl text-gray-600 border border-gray-600 rounded hover:bg-gray-700"
color="white"
>
상류관로 보기
</Button>
</>
)}
</div>
</div>
<div className="flex items-center space-x-3">
{/* 🎯 하수관로일 때만 Infil/RDII 버튼 표시 */}
{layerNm === '하수관로' && (
<>
<Button onClick={() => setIsModalInfil(true)}
className="px-2 py-1 text-xl text-blue-600 border border-blue-600 rounded hover:bg-gray-700"
color="white"
>
침입수분석
</Button>
<Button onClick={() => setIsModalRdii(true)}
className="px-2 py-1 text-xl text-gray-600 border border-gray-600 rounded hover:bg-gray-700"
color="white"
>
RDII분석
</Button>
</>
)}
{layerNm === '하수맨홀' && (
<>
<Button onClick={() => {
setIsLoading(true);
searchUpperPpls(layerSpecificNm).finally(() => setIsLoading(false));
}}
className="px-2 py-1 text-xl text-gray-600 border border-gray-600 rounded hover:bg-gray-700"
color="white"
>
상류관로 보기
</Button>
</>
)}
<Button
onClick={onClose}
className="text-white hover:text-gray-300 focus:outline-none"
>
✕
</Button>
</div>
<Button
onClick={onClose}
// className="text-white hover:text-gray-300 focus:outline-none"
className="absolute top-8 right-4 text-white hover:text-gray-300 focus:outline-none"
size="xs"
>
✕
</Button>
</div>
<div className="overflow-y-auto overflow-x-hidden flex-1" ref={contentRef}>
<VerticalTable
@@ -5,13 +5,14 @@ import React, { useEffect, useMemo, useState } from 'react';
import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table';
//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi';
import { type SelectDataType } from '../commonStore';
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from '../components/common/table';
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES } from '../components/common/table';
import { addFileName, flattenFolders, getFileDownload, useCityCd, useDcTree, useSggCd } from '../components/searchModalReportApi';
// import { useBizByFcltStore } from "../../../store/StoreProvider";
import { getData } from "@/components/lib/api";
import BasicTab, { BASIC_TYPES } from './basicTab';
import OperationTab, { BIZ_TY_CD, operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab';
import ReportTab, { reportTableHeadlist } from './reportTab';
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
@@ -140,10 +141,18 @@ const SearchModal: React.FC<SearchModalProps> = ({
end: "",
});
setShowTable(false);
setTableCount(0);
setTableData([]);
setDetailTableData([]);
setTableCount(0);
setDetailTableCount(0);
setCurPage(1); // 페이지를 1로 초기화
setDetailCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
setIsResetSelection(true); // 선택 초기화
setIsResetDetailSelection(true); // 선택 초기화
// queryParams는 fetchFilteredData 내에서 매번 새로 생성되므로 별도 상태 초기화는 필요 없음
}, [activeTab, basicType, open]);
}, [activeTab, basicType, workType, open]);
useEffect(() => {
setActiveTab(TABS[0]?.val ?? "basic");
@@ -158,13 +167,14 @@ const SearchModal: React.FC<SearchModalProps> = ({
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
// showTable이 true일 때만 fetch (최초 렌더 방지)
setIsResetPage(false);
setIsResetDetailPage(true);
if (showTable) fetchFilteredData();
}, [curPage]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetSelection(true);
setIsResetDetailSelection(true);
setIsResetDetailSelection(false);
// showTable이 true일 때만 fetch (최초 렌더 방지)
if (showTable) {
handleOperationPageNation(Number(detailBizId));
@@ -177,6 +187,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
setIsLoading(true); // 로딩 시작
setIsResetSelection(true); // 선택 초기화
setIsResetDetailSelection(true); // 선택 초기화
try {
let cqlFilters: string[] = [];
if (activeTab === 'basic') {
@@ -201,14 +212,30 @@ const SearchModal: React.FC<SearchModalProps> = ({
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
console.log('필터링된 API 결과(개수):', res.data.count);
console.log('필터링된 API 결과(데이터):', res.data.data);
setTableData(res.data.data || []);
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => {
// 🎯 먼저 각 아이템을 포맷팅
const formattedItem = formatDisplayData(item);
const matched = MS_SWAGE_MHL_CODES.find(code => code.val === item?.mqltCd?.trim());
// 추가 처리 및 일련번호 추가
return {
...formattedItem,
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn, // scsYn이 null이면 'N'으로 변경
mqltCd: matched ? matched.text : item.mqltCd,
};
});
setTableData(dataWithRowNumber);
setTableCount(res.data.count || []);
console.log('tableCount 상태 업데이트 후:', res.data.count); // tableCount 로그 추가
} else if (activeTab === 'operation') {
const RequestDTO: Record<string, string> = {
page: `${curPage - 1}`,
size: '5',
size: '4',
};
if (var1) RequestDTO.bizNm = var1;
if (var2 != '0' && var2) RequestDTO.bizTyCd = var2;
@@ -239,9 +266,11 @@ const SearchModal: React.FC<SearchModalProps> = ({
console.log('필터링된 API 결과(데이터):', res.content);
const mappedContent = (res.content || []).map((item: any) => {
console.log("item:", item);
const formattedItem = formatDisplayData(item);
const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTyCd);
return {
...item,
...formattedItem,
bizTyCd: matched ? matched.lwpoCdNm : item.bizTyCd,
};
});
@@ -293,8 +322,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
setTableData([]);
} finally {
setIsLoading(false); // 로딩 종료
setIsResetSelection(false);
setIsResetDetailSelection(false);
// setIsResetSelection(false);
// setIsResetDetailSelection(false);
}
};
@@ -458,13 +487,17 @@ const SearchModal: React.FC<SearchModalProps> = ({
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch = () => {
setCurPage(1); // 페이지를 1로 초기화
setDetailCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 1로 초기화
setIsResetDetailPage(true); // 페이지를 1로 초기화
setIsResetSelection(true); // 선택 초기화
setIsResetDetailSelection(true); // 선택 초기화
setCurPage(1); // 페이지를 1로 초기화
// setDetailCurPage(1); // 페이지를 1로 초기화
// setIsResetPage(false); // 페이지를 초기화
// setIsResetDetailPage(true); // 디테일 페이지를 초기화
// setIsResetSelection(true); // 선택 초기화
// setIsResetDetailSelection(true); // 선택 초기화
fetchFilteredData();
setIsResetPage(true); // 페이지를 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
setIsResetSelection(false); // 선택 초기화
setIsResetDetailSelection(false); // 선택 초기화
setShowTable(true);
};
@@ -479,6 +512,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
const handleOperationCellClick = async (index?: number | undefined, headId?: String) => {
setIsResetDetailPage(true); // 페이지를 1로 초기화
setIsResetDetailSelection(true); // 선택 초기화
setIsResetDetailPage(true); // 디테일 페이지를 초기화
if (typeof index === "number") {
setIsDetailLoading(true);
@@ -487,8 +521,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
try {
const RequestDTO: Record<string, string> = {
page: `${detailCurPage - 1}`,
size: '5',
bizId: tableData[index % 5].bizId,
size: '4',
bizId: tableData[index % 4].bizId,
};
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
@@ -523,7 +557,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
try {
const RequestDTO: Record<string, string> = {
page: `${detailCurPage - 1}`,
size: '5',
size: '4',
bizId: String(detailBizId),
};
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
@@ -659,7 +693,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
<UiTable
headName={(() => {
const label = BASIC_TYPES.find((type) => type.val === basicType)?.text
return (<>{label} 조회 결과 ({tableCount})건</>);
return (<>{label} 조회 결과 ({tableCount.toLocaleString('ko-KR')})건</>);
})()
}
tableData={tableData}
@@ -684,12 +718,12 @@ const SearchModal: React.FC<SearchModalProps> = ({
headName={
(() => {
const label = WORK_TYPES.find((type) => type.val === workType)?.text
return (<>{label} 사업 현황 ({tableCount})건</>);
return (<>{label} 사업 현황 ({tableCount.toLocaleString('ko-KR')})건</>);
})()
}
tableData={tableData}
headlist={dynamicHeadlist}
pageSize={5}
pageSize={4}
total={tableCount}
cellClick={handleOperationCellClick}
onPageChange={setCurPage}
@@ -706,12 +740,12 @@ const SearchModal: React.FC<SearchModalProps> = ({
headName={
(() => {
const label = WORK_TYPES.find((type) => type.val === workType)?.text
return (<>{label} 결과표 ({detailTableCount})건</>);
return (<>{label} 결과표 ({detailTableCount.toLocaleString('ko-KR')})건</>);
})()
}
tableData={detailTableData}
headlist={dynamicHeadlist2}
pageSize={5}
pageSize={4}
total={detailTableCount}
cellClick={undefined}
onPageChange={setDetailCurPage}
@@ -218,7 +218,7 @@ const BaseLayout = ({
<div className="flex items-center w-full">
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
<>
<Label className="ml-4 w-20 text-xl text-gray-600 whitespace-nowrap">상수사용기간</Label>
<Label className="mr-4 w-20 text-xl text-gray-600 whitespace-nowrap">상수사용기간</Label>
<DateMonthPicker
field={{
value: aplcnYmFrom || (() => { // aplcnYm이 없으면 현재 연월을 기본값으로 설정
@@ -247,7 +247,7 @@ const BaseLayout = ({
setAplcnYmTo?.(value); // string 값을 직접 전달
}
}}
selectClass="ml-5 w-[100px]"
selectClass="w-[100px]"
startYear={1900} // 시작 연도 설정
/>
</>
@@ -258,7 +258,7 @@ const BaseLayout = ({
setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)}
setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달
>
<SelectBox label={"하수처리장"} value={selectedSwageTreatmentPlant} selectArray={treatmentFacilityOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)} selectContentClass="z-[101]" />
<SelectBox label={"하수처리장"} value={selectedSwageTreatmentPlant} selectArray={treatmentFacilityOptions} className="col-span-3" selectClass="ml-5 rounded-lg bg-white truncate" onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)} selectContentClass="z-[101]" />
{/* <SelectBox label={"하수처리구역"} value={selectedSwageProcessingZone} selectArray={processingZoneOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageProcessingZoneChange?.(val)} selectContentClass="z-[101]" /> */}
</GovSidoSigunguSearchForm>
{selectBox?.searchChk ? <SearchFormRight><Button size="sm" onClick={() => selectBox.onSearchClick?.()}>조회</Button></SearchFormRight> : null}
@@ -1,5 +1,5 @@
'use client'
import { deleteData, getData } from '@/components/lib/api'
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState, useEffect } from 'react'
@@ -53,6 +53,8 @@ const tableColumn = [
]
const Result = () => {
const AUTH = useCookieAuth();
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -81,24 +83,36 @@ const Result = () => {
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
useEffect(() => {
// setCurPage(1);
handleSearchButtonClick();
}, [])
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetPage(false);
handleSearchButtonClick();
}, [curPage]);
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch: () => Promise<void> = async () => {
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
await handleSearchButtonClick();
};
const handleSearchButtonClick = async () => {
setIsLoadingResult(true);
setResetSelection(true);
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
const formValues = form.getValues(); // react-hook-form에서 현재 값 가져오기
console.log("formValues", formValues);
let inftwtAnlsSearchDto;
// swageTmplntCd 처리 로직 추가
const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0'
? (() => {
@@ -108,30 +122,21 @@ const Result = () => {
})()
: null;
if (customFormValues) {
inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom,
"aplcnYmTo": formValues.aplcnYmTo,
"enoCd": formValues.enoCd,
"ctpvCd": formValues.ctpvCd,
"sggCd": formValues.sggCd,
"swageTmplntCd": processedSwageTmplntCd,
};
} else {
inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"swageTmplntCd": processedSwageTmplntCd,
};
}
let inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"swageTmplntCd": processedSwageTmplntCd,
"auth": AUTH,
};
console.log("Search DTO:", inftwtAnlsSearchDto);
const queryParams = new URLSearchParams({
page: '0',
size: '10000000',
page: `${curPage - 1}`,
size: '10',
sort: 'rainInfwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
@@ -153,14 +158,13 @@ const Result = () => {
// 추가 처리 및 일련번호 추가
return {
...formattedItem,
rowNumber: index + 1, // 1부터 시작하는 일련번호 추가
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
};
});
// 선택된 행 초기화
setSelectedRowData(null);
setSelectedRowIndex(undefined);
setTableData(dataWithRowNumber);
setTotalData(dataWithRowNumber.length);
@@ -173,6 +177,7 @@ const Result = () => {
alert("분석결과 조회 중 오류가 발생했습니다.");
setTableData([]);
setTotalData(0);
setSelectedRowData(null);
}
setResetSelection(false);
};
@@ -181,7 +186,6 @@ const Result = () => {
const handleCellClick = (index: number | undefined, headId: string) => {
if (index !== undefined && tableData[index]) {
setSelectedRowData(tableData[index]);
setSelectedRowIndex(index);
console.log("Selected row data:", tableData[index]);
}
};
@@ -258,16 +262,8 @@ const Result = () => {
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 삭제 후 테이블 새로고침을 위해 재검색
const customFormValues: Partial<ResultFormValues> = {
aplcnYmFrom: selectedRowData.aplcnYmFrom || null,
aplcnYmTo: selectedRowData.aplcnYmTo || null,
enoCd: selectedRowData.enoCd || null,
ctpvCd: selectedRowData.ctpvCd || null,
sggCd: selectedRowData.sggCd || null,
swageTmplntCd: selectedRowData.swageTmplntCd || null,
};
await handleSearchButtonClick(customFormValues);
// 검색 버튼 클릭 이벤트 트리거
await handleSearchButtonClick();
setIsLoadingResult(false);
@@ -285,7 +281,7 @@ const Result = () => {
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearch }}
titleName={{ mainTitle: '분석결과', firstCardTitle: 'RDII 산정결과 현황' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
@@ -320,10 +316,10 @@ const Result = () => {
tableData={tableData}
total={totalData}
pageSize={10}
children={undefined}
cellClick={handleCellClick}
onPageChange={(e) => { }}
onPageChange={setCurPage}
resetSelection={resetSelection}
resetPageNo={isResetPage}
/>
</div>
</div>
@@ -74,26 +74,41 @@ const Result = () => {
}
})
const AUTH = useCookieAuth();
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [totalData, setTotalData] = useState(0) //총 데이터 수l
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [selectedRowIndex, setSelectedRowIndex] = useState<number | undefined>(undefined);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
useEffect(() => {
// setCurPage(1);
handleSearchButtonClick();
}, [])
const auth = useCookieAuth();
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetPage(false);
handleSearchButtonClick();
}, [curPage]);
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch: () => Promise<void> = async () => {
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
await handleSearchButtonClick();
};
const handleSearchButtonClick = async () => {
setIsLoadingResult(true);
setResetSelection(true);
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
const formValues = form.getValues(); // react-hook-form에서 현재 값 가져오기
console.log("formValues", formValues);
let inftwtAnlsSearchDto;
// swageTmplntCd 처리 로직 추가
const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0'
@@ -104,34 +119,22 @@ const Result = () => {
})()
: null;
if (customFormValues) {
inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom,
"aplcnYmTo": formValues.aplcnYmTo,
"enoCd": formValues.enoCd,
"ctpvCd": formValues.ctpvCd,
"sggCd": formValues.sggCd,
"swageTmplntCd": processedSwageTmplntCd,
// "swtrmtZoneCd": formValues.swtrmtZoneCd,
"auth" : auth,
};
} else {
inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"swageTmplntCd": processedSwageTmplntCd,
// "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd,
"auth" : auth,
};
}
let inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"swageTmplntCd": processedSwageTmplntCd,
// "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd,
"auth": AUTH,
};
console.log("Search DTO:", inftwtAnlsSearchDto);
const queryParams = new URLSearchParams({
page: '0',
size: '10000000',
page: `${curPage - 1}`,
size: '10',
sort: 'inftwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
@@ -144,6 +147,7 @@ const Result = () => {
console.log("API: ", `v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
try {
const data = await getData(`v1/inftwtAnls${queryString ? `?${queryString}` : ''}`);
console.log("API response:", data);
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
const dataWithRowNumber = (data.content || []).map((item: any, index: number) => {
@@ -153,17 +157,16 @@ const Result = () => {
// 추가 처리 및 일련번호 추가
return {
...formattedItem,
rowNumber: index + 1, // 1부터 시작하는 일련번호 추가
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
};
});
// 선택된 행 초기화
setSelectedRowData(null);
setSelectedRowIndex(undefined);
setTableData(dataWithRowNumber);
setTotalData(dataWithRowNumber.length);
setTotalData(data?.totalElements || 0); // totalElements가 없을 경우 0으로 설정
console.log("response:", data);
console.log("response data:", dataWithRowNumber);
setIsLoadingResult(false);
@@ -174,7 +177,6 @@ const Result = () => {
setTableData([]);
setTotalData(0);
setSelectedRowData(null);
setSelectedRowIndex(undefined);
}
setResetSelection(false);
};
@@ -183,7 +185,6 @@ const Result = () => {
const handleCellClick = (index: number | undefined, headId: string) => {
if (index !== undefined && tableData[index]) {
setSelectedRowData(tableData[index]);
setSelectedRowIndex(index);
console.log("Selected row data:", tableData[index]);
}
};
@@ -262,17 +263,8 @@ const Result = () => {
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 삭제 후 테이블 새로고침을 위해 재검색
const customFormValues: Partial<ResultFormValues> = {
aplcnYmFrom: selectedRowData.aplcnYmFrom || null,
aplcnYmTo: selectedRowData.aplcnYmTo || null,
enoCd: selectedRowData.enoCd || null,
ctpvCd: selectedRowData.ctpvCd || null,
sggCd: selectedRowData.sggCd || null,
swageTmplntCd: selectedRowData.swageTmplntCd || null,
// swtrmtZoneCd: selectedRowData.swtrmtZoneCd || null,
};
await handleSearchButtonClick(customFormValues);
// 검색 버튼 클릭 이벤트 트리거
await handleSearchButtonClick();
setIsLoadingResult(false);
@@ -287,10 +279,9 @@ const Result = () => {
};
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearchButtonClick }}
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearch }}
titleName={{ mainTitle: '분석결과', firstCardTitle: '분석이력' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
@@ -325,10 +316,10 @@ const Result = () => {
tableData={tableData}
total={totalData}
pageSize={10}
children={undefined}
cellClick={handleCellClick}
onPageChange={(e) => { }}
onPageChange={setCurPage}
resetSelection={resetSelection}
resetPageNo={isResetPage}
/>
</div>
</div>
@@ -27,6 +27,25 @@ const Page = () => {
const [isLoading, setIsLoading] = useState(true) //로딩 상태
const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
const [isResetPage, setIsResetPage] = useState<boolean>(false);
useEffect(() => {
setCurPage(1)
fetchData()
}, [])
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetPage(false);
fetchData();
}, [curPage]);
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch: () => Promise<void> = async () => {
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
await fetchData();
};
// 조회
const fetchData = async () => {
@@ -60,11 +79,6 @@ const Page = () => {
}
}
useEffect(() => {
setCurPage(1)
fetchData()
}, [])
// 파일 다운로드
const isChckFile = () => {
if (fileNm === '' || fileNm === undefined) {
@@ -106,7 +120,7 @@ const Page = () => {
if (data) {
alert('파일이 등록되었습니다.');
setIsModalOpen(false);
fetchData();
handleSearch();
}
}
@@ -160,7 +174,7 @@ const Page = () => {
</SearchFormLeft>
<SearchFormRight>
<div className='flex items-center gap-5'>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await fetchData()} >조회</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await handleSearch()} >조회</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={handleRegisterClick} >등록</Button>
</div>
</SearchFormRight>
@@ -188,8 +202,9 @@ const Page = () => {
setSbmrgPrevntBizSn(tableData[index]?.sbmrgPrevntBizSn);
}
}}
onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}
onPageChange={setCurPage}
resetSelection={resetSelection}
resetPageNo={isResetPage}
/>
</ListDiv>
{/* 파일 업로드 모달 */}
@@ -25,6 +25,25 @@ const Page = () => {
const [isLoading, setIsLoading] = useState(true) //로딩 상태
const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
const [isResetPage, setIsResetPage] = useState<boolean>(false);
useEffect(() => {
setCurPage(1)
fetchData()
}, [])
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
setIsResetPage(false);
fetchData();
}, [curPage]);
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
const handleSearch: () => Promise<void> = async () => {
setCurPage(1); // 페이지를 1로 초기화
setIsResetPage(true); // 페이지를 초기화
await fetchData();
};
// 데이터 조회
const fetchData = async () => {
@@ -57,11 +76,6 @@ const Page = () => {
}
}
useEffect(() => {
setCurPage(1)
fetchData()
}, [])
// 파일 다운로드
const isChckFile = () => {
if (fileNm === '' || fileNm === undefined) {
@@ -103,7 +117,7 @@ const Page = () => {
if (data) {
alert('파일이 등록되었습니다.');
setIsModalOpen(false);
fetchData();
handleSearch();
}
}
@@ -155,7 +169,7 @@ const Page = () => {
</SearchFormLeft>
<SearchFormRight>
<div className='flex items-center gap-5'>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await fetchData()} >조회</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={async () => await handleSearch()} >조회</Button>
<Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={handleRegisterClick} >등록</Button>
</div>
</SearchFormRight>
@@ -184,7 +198,7 @@ const Page = () => {
setDcrpPplMtncBizSn(tableData[index]?.dcrpPplMtncBizSn);
}
}}
onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}
onPageChange={setCurPage}
resetSelection={resetSelection}
/>
</ListDiv>
@@ -1,5 +1,5 @@
import { dateFields, numberFields, yearMonthFields, dateTimeFields } from "../../../(map-layout)/map/components/common/table";
import { isValidDateString, formatDateString } from "./date";
import { formatDateString } from "./date";
// 🎯 데이터 객체 전체를 포맷팅하는 함수
export const formatDisplayData = (data: { [key: string]: any }): { [key: string]: string } => {