feat: 중점우수토실 상세보기 API 변경하여 구현

This commit is contained in:
swcho
2025-07-14 20:46:44 +09:00
parent 8a874dd114
commit ab492fc842
6 changed files with 179 additions and 102 deletions
+8 -4
View File
@@ -125,8 +125,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
// 로딩바
const [isLoading, setIsLoading] = useState(false);
// 상세 정보 조회를 위한 상태 추가
const [popupFeature, setPopupFeature] = useState<any>(null);
const sidos = useCityCd();
const siguns = useSggCd();
@@ -173,7 +171,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
};
// cellClick 함수 타입 지정
const handleCellClick = (index?: number, headId?: string) => {
const handleCellClick = (index?: number) => {
if (typeof index === "number") {
onFeatureSelect(tableData[index % 10]); // 10은 page size 하드 코딩한 숫자
onOpenChange(false); // SearchModal 닫기
@@ -339,6 +337,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
return cqlFilters;
};
// 하수처리시설용 CQL 필터 생성
const facilityCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -367,6 +366,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
if (변수1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${변수1}%'`);
return cqlFilters;
};
// 하수처리구역용 CQL 필터 생성
const zoneCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -376,6 +376,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
if (변수2) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${변수2}%'`);
return cqlFilters;
};
// 하수맨홀용 CQL 필터 생성
const mhCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -404,6 +405,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
return cqlFilters;
};
// 하수펌프장용 CQL 필터 생성
const psmtnCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -437,6 +439,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
return cqlFilters;
};
// 우수토실용 CQL 필터 생성
const otflCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -466,6 +469,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
return cqlFilters;
};
// 운영정보용 CQL 필터 생성
const operationCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -487,6 +491,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
);
return cqlFilters;
};
// 하수도정비기본계획보고서용 CQL 필터 생성
const reportCqlFilters = () => {
const cqlFilters: string[] = [];
@@ -1092,7 +1097,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
<UiTable2
headName={
(() => {
const resultCount = tableData.length;
const label =
activeTab === "basic"
? BASIC_TYPES.find((type) => type.val === basicType)?.text
@@ -9,7 +9,7 @@ type LayerComponentProps = {
featureData: any;
isMainSsc?: boolean;
isDrtwSewagePump?: boolean;
additionalData?: any;
rainMeasureData?: any;
onSaveClick?: () => void;
onFormDataChange?: (data: any) => void; // 추가
};
@@ -619,25 +619,25 @@ export const untrtSwgRainCol2 = [
// 중점우수토실 강우정보 (id값 틀릴 수 있음)
export const untrtSwgRainCol3 = [
{ id: 'rain_begin_dt', title: '강우시작일시' },
{ id: 'rain_end_dt', title: '강우종료일시' },
{ id: 'precdng_dry', title: '선행건기(h)' },
{ id: 'rain_info', title: '강우정보' },
{ id: 'rain_fall', title: '누적강우량(mm)' },
{ id: 'cntnc_time', title: '지속시간(h)' },
{ id: 'rain_in', title: '강우강도(mm/h)' }
{ id: 'rainBeginDt', title: '강우시작일시' },
{ id: 'rainEndDt', title: '강우종료일시' },
{ id: 'precdngDry', title: '선행건기(h)' },
{ id: 'rainInfo', title: '강우정보' },
{ id: 'rainFall', title: '누적강우량(mm)' },
{ id: 'cntncTime', title: '지속시간(h)' },
{ id: 'rainIn', title: '강우강도(mm/h)' }
];
// 중점우수토실 측정정보 (id값 틀릴 수 있음)
export const untrtSwgRainCol4 = [
{ id: 'mesure_begin_dt', title: '측정시작일시' },
{ id: 'mesure_end_dt', title: '측정종료일시' },
{ id: 'mesure_intrvl', title: '측정간격(분)' },
{ id: 'mesure_mhrls_instl_lc', title: '측정기기 설치위치' },
{ id: 'flwmtr_combined', title: '유량계 종류 및 모델명' }, // 합쳐진 컬럼
{ id: 'qltwtr_begin_dt', title: '채수시작일시' },
{ id: 'qltwtr_end_dt', title: '채수종료일시' },
{ id: 'qltwtr_co', title: '채수횟수' }
{ id: 'mesureBeginDt', title: '측정시작일시' },
{ id: 'mesureEndDt', title: '측정종료일시' },
{ id: 'mesureIntrvl', title: '측정간격(분)' },
{ id: 'mesureMhrlsInstlLc', title: '측정기기 설치위치' },
{ id: 'flwmtrCombined', title: '유량계 종류 및 모델명' }, // 합쳐진 컬럼
{ id: 'qltwtrBeginDt', title: '채수시작일시' },
{ id: 'qltwtrEndDt', title: '채수종료일시' },
{ id: 'qltwtrCo', title: '채수횟수' }
];
// 중점우수토실 수량/수질측정 정보
@@ -1,27 +1,31 @@
// 우수토실
import { UiModal, UiTable } from '@common/business_components/ui';
import { Button, TableHead, TableRow } from '@common/components/ui';
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { untrtSwgRainCol3, untrtSwgRainCol4, untrtSwgRainCol5 } from '../common/table';
import { VerticalTable } from '@/components/ui/verticalTable';
import LoadingOverlay from '@/components/ui/LoadingBar';
import { getData } from "@/components/lib/api";
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
const inputColumn = [
{ id: "prrtymngYn", title: "중점여부", type: "select", options: ["중점우수토실", "우수토실"] },
{ id: "swtrmtZoneCd", title: "처리구역", type: "input" },
];
const Ssc = ({ featureData, isMainSsc, additionalData, onSaveClick }: {
const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: {
featureData: any,
isMainSsc?: boolean,
additionalData?: any,
rainMeasureData?: any,
onSaveClick?: () => void
}) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const [formData, setFormData] = useState<any>({});
console.log('featureData data received:', featureData); // 추가 데이터 확인용
console.log('Additional data received:', additionalData); // 추가 데이터 확인용
const [mntrngsData, setMntrngsData] = useState<any[]>(); // 수량/수질 측정 정보
const [selectedRowIndex, setSelectedRowIndex] = useState<number>(-1);
// console.log('featureData data received:', featureData); // 추가 데이터 확인용
// console.log('rainMeasureData received:', rainMeasureData); // 추가 데이터 확인용
const openModal = () => {
setIsModalOpen(true);
@@ -54,17 +58,75 @@ const Ssc = ({ featureData, isMainSsc, additionalData, onSaveClick }: {
}
};
// 테이블 셀 클릭 핸들러 추가
const handleCellClick = async (index: number | undefined, headId: string) => {
if (index !== undefined && rainMeasureData) {
const data = await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo);
setMntrngsData(data);
setSelectedRowIndex(index);
}
};
// 🎯 추가 데이터 API 호출 함수 (독립적으로 실행)
const fetchMntrngsData = useCallback(async (adstDstrCd: string, manageNo: string) => {
if (!adstDstrCd || !manageNo) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo });
return [];
}
try {
const queryParams = new URLSearchParams({
pageable: `{}`,
});
console.log(`SSC 추가 데이터 API 호출 시작: v1/onsemiro/swgRain?${queryParams.toString()}`);
const result = await getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`);
console.log('SSC 추가 데이터 API 호출 성공:', result);
// 🎯 result가 페이지네이션 객체인 경우 content 배열 추출
let dataArray: any[] = [];
if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) {
dataArray = result.content;
} else if (Array.isArray(result)) {
dataArray = result;
} else {
console.warn('예상치 못한 API 응답 형식:', result);
return [];
}
// 🎯 각 아이템을 전처리하여 반환
const processedDataArray = dataArray.map((data: any) => {
// 🎯 formatDisplayData로 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
// 🎯 추가 데이터 조합
return {
...formattedItem,
};
});
console.log('processedDataArray', processedDataArray);
return processedDataArray;
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
}, []);
const renderModalBody = () => (
<div className='grid grid-cols-1 gap-4 p-4'>
{/* 강우정보 테이블 */}
<UiTable
headName='강우정보'
tableData={featureData}
tableData={Array.isArray(rainMeasureData) ? rainMeasureData : []}
headlist={untrtSwgRainCol3}
pageSize={10}
total={0}
children={undefined}
cellClick={undefined}
total={Array.isArray(rainMeasureData) ? rainMeasureData.length : 0}
cellClick={handleCellClick}
onPageChange={function (page: number): void {
console.log('Rain data page change:', page);
}}
@@ -73,14 +135,17 @@ const Ssc = ({ featureData, isMainSsc, additionalData, onSaveClick }: {
{/* 측정정보 테이블 */}
<UiTable
headName='측정정보'
tableData={featureData}
tableData={Array.isArray(rainMeasureData) ? rainMeasureData : []}
headlist={untrtSwgRainCol4}
pageSize={10}
total={0}
cellClick={undefined}
total={Array.isArray(rainMeasureData) ? rainMeasureData.length : 0}
cellClick={handleCellClick}
// onPageChange={setCurPage}
onPageChange={function (page: number): void {
console.log('Measure data page change:', page);
console.log('Rain data page change:', page);
}}
resetPageNo={false}
// index={curPage}
>
{/* 커스텀 헤더 */}
<TableRow>
@@ -102,11 +167,10 @@ const Ssc = ({ featureData, isMainSsc, additionalData, onSaveClick }: {
{/* 수량/수질측정 정보 테이블 */}
<UiTable
headName='수량/수질측정 정보'
tableData={additionalData}
tableData={Array.isArray(mntrngsData) ? mntrngsData : []}
headlist={untrtSwgRainCol5}
pageSize={10}
total={additionalData.length}
children={undefined}
total={Array.isArray(mntrngsData) ? mntrngsData.length : 0}
cellClick={undefined}
onPageChange={function (page: number): void {
console.log('Quality data page change:', page);
@@ -124,10 +188,9 @@ const Ssc = ({ featureData, isMainSsc, additionalData, onSaveClick }: {
headlist={inputColumn}
pageSize={0}
total={0}
children={undefined}
cellClick={undefined}
isEditable={true}
onPageChange={function (page: number): void {
onPageChange={function (): void {
throw new Error('Function not implemented.');
}}
showSaveButton={true}
@@ -18,6 +18,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
const [rainMeasureData, setRainMeasureData] = useState(false); // 강우/측정 측정 정보
const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
@@ -27,13 +28,10 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
const [isMainSsc, setIsMainSsc] = useState(false); // 중점우수토실 여부
const [isDrtwSewagePump, setIsDrtwSewagePump] = useState(false); // 오수펌프장 여부
const [isLoading, setIsLoading] = useState(false);
// 🎯 추가 데이터 로딩 상태 분리
const [isAdditionalDataLoading, setIsAdditionalDataLoading] = useState(false);
const [additionalDataError, setAdditionalDataError] = useState<string | null>(null);
// 🎯 InfilChart 및 RdiiChart 관련 상태 추가
const { setIsOpen: setIsModalInfil } = useInfilChartModalStore();
const { setIsOpen: setIsModalRdii } = useRdiiChartModalStore();
const [curPage, setCurPage] = useState(1); // 현재 페이지 상태
// 🎯 관로관리번호 추출
const pplMngNo = featureData?.values_?.fclt_mng_no || "";
@@ -44,41 +42,13 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
: featureData.fid;
console.log('featureId', featureId);
// 🎯 추가 데이터 API 호출 함수 (독립적으로 실행)
const fetchAdditionalData = useCallback(async (adstDstrCd: string, manageNo: string) => {
if (!adstDstrCd || !manageNo) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo });
return [];
}
setIsAdditionalDataLoading(true);
setAdditionalDataError(null);
try {
console.log(`추가 데이터 API 호출 시작: v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs`);
const result = getData(`v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs`);
console.log('추가 데이터 API 호출 성공:', result);
return result || [];
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
setAdditionalDataError(error instanceof Error ? error.message : '알 수 없는 오류');
return [];
} finally {
setIsAdditionalDataLoading(false);
}
}, []);
// 🎯 데이터 전처리 메모이제이션
const processedData = useMemo(() => {
if (!featureData?.values_) return null;
const data = featureData.values_;
return {
...data,
discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}`,
flwmtr_combined: `${data.flwmtr_mesure_tp_etc || '-'} / ${data.flwmtr_knd || '-'}`
...featureData.values_,
discharge_pipe_spec: `직경: ${featureData.values_.pipe_diameter || '-'} / 높이: ${featureData.values_.pipe_hg || '-'} / 길이: ${featureData.values_.pipe_lt || '-'}`
};
}, [featureData?.values_]);
@@ -86,7 +56,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
const getSelectedLayers = () => {
let result = null;
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수
let featureLayer: { get: (key: string) => any }; // 피처가 속한 레이어를 찾기 위한 변수
layers.forEach((layer: any) => {
const source = layer.getSource();
if (source && source.hasFeature && featureData
@@ -148,7 +118,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
setLayerNm(selectedLayerName); // 상태 업데이트
console.log('gis data', featureData.values_);
let data = featureData.values_;
const data = featureData.values_;
// 하수맨홀인 경우 용도코드를 텍스트로 변환
if (selectedLayerName === '하수맨홀' && data) {
@@ -162,7 +132,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
}
// 🎯 기본 데이터 포맷팅
let formattedData = formatDisplayData(data);
const formattedData = formatDisplayData(data);
setfetchData(formattedData);
setTableData(formattedData);
@@ -187,18 +157,18 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
setfetchData([processedFormattedData])
setTableData(processedFormattedData);
setIsLoading(false); // 기본 렌더링 완료
// setIsLoading(false); // 기본 렌더링 완료
// 수량/수질측정 정보 데이터 호출
if (data?.adst_dstr_cd && data?.manage_no) {
fetchAdditionalData(data.adst_dstr_cd, data.manage_no)
// 강우/측정 정보 데이터 호출
if (data?.chmbr_xcnts && data?.chmbr_ydnts) {
fetchRainMeasureData(data.chmbr_xcnts, data.chmbr_ydnts)
.then(response => {
console.log('additionalResponse', response);
setMntrngsData(response || []);
setRainMeasureData(response || []);
})
.catch(error => {
console.error('추가 데이터 로딩 실패:', error);
setMntrngsData(false);
setRainMeasureData(false);
});
}
break;
@@ -231,7 +201,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
// API 엔드포인트 선택
let apiEndpoint = '';
let getFeatureId = featureId.toString().split('.').pop() || null;
const getFeatureId = featureId.toString().split('.').pop() || null;
if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${getFeatureId}`;
else if (selectedLayerName === '우수토실') apiEndpoint = `v1/fcltstofcbr/${getFeatureId}`;
else if (selectedLayerName === '오수펌프' || selectedLayerName === '빗물펌프') apiEndpoint = `v1/fcltpump/${getFeatureId}`;
@@ -289,25 +259,75 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
fetchData();
}, [featureId, processedData]);
// 🎯 추가 데이터 API 호출 함수 (독립적으로 실행)
const fetchRainMeasureData = useCallback(async (chmbrXcnts: string, chmbrYdnts: string) => {
if (!chmbrXcnts || !chmbrYdnts) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts });
return [];
}
try {
const queryParams = new URLSearchParams({
chmbrXcnts: chmbrXcnts,
chmbrYdnts: chmbrYdnts,
pageable: `{}`,
});
console.log(`추가 데이터 API 호출 시작: v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`);
const result = await getData(`v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`);
console.log('추가 데이터 API 호출 성공:', result);
// 🎯 result가 페이지네이션 객체인 경우 content 배열 추출
let dataArray: any[] = [];
if (result && typeof result === 'object' && 'content' in result && Array.isArray(result.content)) {
dataArray = result.content;
} else if (Array.isArray(result)) {
dataArray = result;
} else {
console.warn('예상치 못한 API 응답 형식:', result);
return [];
}
// 🎯 각 아이템을 전처리하여 반환
const processedDataArray = dataArray.map((data: any) => {
// 🎯 formatDisplayData로 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
// 🎯 추가 데이터 조합
return {
...formattedItem,
flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`,
};
});
return processedDataArray;
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
}, []);
// 🎯 추가 데이터는 별도 useEffect로 비동기 로딩
useEffect(() => {
if (layerNm === '중점우수토실' && featureData?.values_) {
const data = featureData.values_;
if (data?.adst_dstr_cd && data?.manage_no) {
if (data?.chmbr_xcnts && data?.chmbr_ydnts) {
// 🎯 추가 데이터를 비동기로 로딩 (UI 블로킹 없음)
fetchAdditionalData(data.adst_dstr_cd, data.manage_no)
fetchRainMeasureData(data.chmbr_xcnts, data.chmbr_ydnts)
.then(response => {
console.log('추가 데이터 로딩 완료:', response);
setMntrngsData(response || []);
setRainMeasureData(response || []);
})
.catch(error => {
console.error('추가 데이터 최종 로딩 실패:', error);
setMntrngsData(false);
setRainMeasureData(false);
});
}
}
}, [layerNm, featureData?.values_, fetchAdditionalData]);
}, [layerNm, featureData?.values_, fetchRainMeasureData]);
// 저장 시 모달 띄우기
const handleSaveClick = () => {
@@ -434,9 +454,8 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
headlist={headlist}
pageSize={20}
total={0}
children={undefined}
cellClick={undefined}
onPageChange={function (page: number): void {
onPageChange={function (): void {
throw new Error("Function not implemented.");
}}
// 두 번째 테이블 조건부 props
@@ -451,10 +470,10 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
<Modal isModalOpen={isModalOpen} title="자체입력정보 저장" setIsModalOpen={setIsModalOpen} closeModal={handleModalClose} saveModal={handleModalSave}><div className="text-lg">정보를 저장하시겠습니까?</div></Modal>
{ContentComponent &&
<ContentComponent
featureData={fetchData}
featureData={tableData}
isMainSsc={isMainSsc}
isDrtwSewagePump={isDrtwSewagePump}
additionalData={mntrngsData}
rainMeasureData={rainMeasureData}
onSaveClick={handleSaveClick}
/>
}
+1 -10
View File
@@ -9,7 +9,7 @@ type UiTableProps = {
headlist: UiHeadProps[];
pageSize: number;
total: number;
children: Record<string, React.ReactNode> | undefined;
children?: Record<string, React.ReactNode> | undefined;
cellClick: ((index: number | undefined, headId: string) => void) | undefined;
onPageChange: (page: number) => void;
className?: string;
@@ -48,21 +48,12 @@ export function VerticalTable({
pageSize,
total,
children,
cellClick,
onPageChange,
className = "",
resetPageNo = false,
isEditable = false,
tableName2,
tableData2,
headlist2,
pageSize2,
total2,
children2,
cellClick2,
onPageChange2,
className2 = "",
resetPageNo2 = false,
isEditable2 = false,
showSecondTable = false,
showSaveButton = false,