Files
frontend/apps/sn/app/(map-layout)/map/components/popupComponent/Ssc.tsx
T

353 lines
13 KiB
TypeScript

/**
* ➡️ 경로 : 하수관망도 메인 페이지 (시설물 클릭) - 오른쪽 팝업창 (우수토실)
*
* 📄 Ssc
* 우수토실 정보 컴포넌트
*
* - 우수토실의 자체입력정보 표시 및 편집 기능
* - 중점우수토실 운영정보 모달 표시 (강우/측정 정보, 수량/수질측정 정보)
* - VerticalTable을 통한 입력 폼 렌더링
* - 저장 버튼을 통한 데이터 저장 기능
*
* 🔗 사용 API
* - 위경도로 강우시 미처리 하수 목록 조회: GET /api/v1/onsemiro/getSwgRainsByGeomety (sewerage-service)
* - 강우시 월류 하수 관리 모니터링 조회: GET /api/v1/onsemiro/swgRain/{adstDstrCd}/{manageNo}/mntrngs (sewerage-service)
*/
import { UiModal, UiTable } from '@common/business_components/ui';
import { Button, TableHead, TableRow } from '@common/components/ui';
import React, { useState, useEffect } from 'react';
import { untrtSwgRainCol3, untrtSwgRainCol4, untrtSwgRainCol5 } from '../common/table';
import { VerticalTable } from '@/components/ui/verticalTable';
import { getData } from "@/components/lib/api";
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import { Label } from '@radix-ui/react-label';
const inputColumn = [
{ id: "prrtymngYn", title: "중점여부", type: "select", options: ["우수토실", "중점우수토실"] },
{ id: "swtrmtZoneNm", title: "처리구역", type: "input" },
];
const Ssc = ({ featureData, isMainSsc, chmbrXcnts, chmbrYdnts, onSaveClick }: {
featureData: any,
isMainSsc?: boolean,
chmbrXcnts?: string,
chmbrYdnts?: string,
onSaveClick?: () => void
}) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const [formData, setFormData] = useState<any>({});
const [rainMeasureData, setRainMeasureData] = useState<any>({}); // 강우, 측정 정보
const [mntrngsData, setMntrngsData] = useState<any[]>([]); // 수량/수질측정 정보
const [totalCount, setTotalCount] = useState<number>(0);
const [totalDetailCount, setTotalDetailCount] = useState<number>(0);
const [selectedRowIndex, setSelectedRowIndex] = useState<number>(-1);
const [curPage, setCurPage] = useState<number>(1);
const [curDetailPage, setCurDetailPage] = useState<number>(1);
const [isResetPage, setIsResetPage] = useState<boolean>(true);
const [isResetDetailPage, setIsResetDetailPage] = useState<boolean>(true);
const [isResetSelection, setIsResetSelection] = useState<boolean>(false);
// 강우, 측정 정보 API 호출 함수
const fetchRainMeasureData = async (chmbrXcnts: string, chmbrYdnts: string) => {
setIsResetDetailPage(true);
setIsResetSelection(true);
if (!chmbrXcnts || !chmbrYdnts) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts });
return [];
}
try {
const queryParams = new URLSearchParams({
chmbrXcnts: chmbrXcnts,
chmbrYdnts: chmbrYdnts,
page: `${curPage - 1}`,
size: '3',
});
let result;
const res = await fetch(`/sewerage-service/api/v1/onsemiro/getSwgRainsByGeomety?${queryParams.toString()}`, {
method: "GET",
});
if (res.ok) {
// 응답이 비어있는지 확인
const contentLength = res.headers.get('content-length');
if (contentLength === '0') result = null;
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
const text = await res.text();
if (!text || text.trim() === '') result = null;
// JSON 파싱 시도
result = JSON.parse(text);
}
// helper: "YYYY-MM-DD HH:MM:SS" -> "MM-DD HH:MM"
const formatNoYearNoSec = (val: any) => {
if (!val && val !== 0) return "-";
const s = String(val);
// 정규식으로 안전하게 매칭 후 변환
const m = s.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::\d{2})?$/);
if (m) {
const [, , mm, dd, hh, min] = m;
return `${mm}-${dd} ${hh}:${min}`;
}
// 포맷이 예상과 다르면, 길이에 따라 잘라보기
if (s.length >= 16) return s.substring(5, 16); // "YYYY-" 제거하고 초 전까지
return s;
};
const processedDataArray: any[] = result.content.map((data: any) => {
// 🎯 formatDisplayData로 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
// 🎯 추가 데이터 조합
return {
...formattedItem,
flwmtrCombined: `${data.flwmtrMesureTpEtc || '-'} / ${data.flwmtrKnd || '-'}`,
mesureBeginDt: data.mesureBeginDt ? formatNoYearNoSec(data.mesureBeginDt) : '-',
mesureEndDt: data.mesureEndDt ? formatNoYearNoSec(data.mesureEndDt) : '-',
qltwtrBeginDt: data.qltwtrBeginDt ? formatNoYearNoSec(data.qltwtrBeginDt) : '-',
qltwtrEndDt: data.qltwtrEndDt ? formatNoYearNoSec(data.qltwtrEndDt) : '-',
};
});
setRainMeasureData(processedDataArray);
setTotalCount(result?.totalElements || 0);
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
};
// 수량/수질측정 정보 API 호출 함수
const fetchMntrngsData = async (adstDstrCd: string, manageNo: string) => {
setIsResetDetailPage(true);
if (!adstDstrCd || !manageNo) {
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { adstDstrCd, manageNo });
return [];
}
try {
const queryParams = new URLSearchParams({
page: `${curDetailPage - 1}`,
size: '3',
});
let result;
const res = await fetch(`/sewerage-service/api/v1/onsemiro/swgRain/${adstDstrCd}/${manageNo}/mntrngs?${queryParams.toString()}`, {
method: "GET",
});
if (res.ok) {
// 응답이 비어있는지 확인
const contentLength = res.headers.get('content-length');
if (contentLength === '0') result = null;
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
const text = await res.text();
if (!text || text.trim() === '') result = null;
// JSON 파싱 시도
result = JSON.parse(text);
}
// 각 아이템 전처리
const processedDataArray = result.content.map((data: any) => {
// 기본 포맷팅 적용
const formattedItem = formatDisplayData(data);
return {
...formattedItem,
};
});
setMntrngsData(processedDataArray);
setTotalDetailCount(result?.totalElements || 0);
} catch (error) {
console.error('추가 데이터 로딩 실패:', error);
return [];
}
};
// 🎯 추가 데이터는 별도 useEffect로 비동기 로딩
useEffect(() => {
// 강우/측정 정보 데이터 호출
if (isMainSsc && chmbrXcnts && chmbrYdnts) {
fetchRainMeasureData(chmbrXcnts, chmbrYdnts)
}
}, [chmbrXcnts, chmbrYdnts]);
const openModal = () => {
setIsModalOpen(true);
};
const closeModal = () => {
setIsModalOpen(false);
if (!isDataLoaded) {
setTimeout(() => setIsDataLoaded(true), 100);
}
};
// featureData가 변경될 때 formData 초기화
useEffect(() => {
if (featureData) {
setFormData(featureData);
setMntrngsData([]);
}
}, [featureData]);
// featureData가 변경될 때 formData 초기화
useEffect(() => {
// showTable이 true일 때만 fetch (최초 렌더 방지)
setIsResetDetailPage(false);
setSelectedRowIndex(-1);
if (chmbrXcnts && chmbrYdnts) fetchRainMeasureData(chmbrXcnts, chmbrYdnts);
}, [curPage]);
// 페이지 변경 시마다 데이터 fetch
useEffect(() => {
if (chmbrXcnts && chmbrYdnts && selectedRowIndex !== -1) fetchMntrngsData(rainMeasureData[selectedRowIndex].adstDstrCd, rainMeasureData[selectedRowIndex].manageNo);
}, [curDetailPage]);
// 폼 데이터가 변경될 때 호출될 핸들러
const handleFormDataChange = (newFormData: any) => {
setFormData(newFormData);
};
// 저장 버튼 클릭 시 최신 formData를 부모에게 전달
const handleSaveClick = () => {
if (onSaveClick) {
// 저장 전에 formData를 부모 컴포넌트의 handleSaveClick에 전달
onSaveClick();
}
};
// 테이블 셀 클릭 핸들러 추가
const handleCellClick = async (index: number | undefined, headId: string) => {
if (index !== undefined && rainMeasureData) {
await fetchMntrngsData(rainMeasureData[index].adstDstrCd, rainMeasureData[index].manageNo);
setSelectedRowIndex(index);
}
};
const renderModalBody = () => (
<div className='grid grid-cols-1 gap-4 p-4 mr-4 mt-4'>
<div className='border rounded-lg pt-4 bg-white'>
<Label className='text-2xl font-bold text-label ml-4'>강우/측정 정보</Label>
<hr className="my-4 ml-4 mr-4 border-t border-gray-300" />
{/* 강우정보 테이블 */}
<UiTable
headName=''
tableData={rainMeasureData}
headlist={untrtSwgRainCol3}
pageSize={3}
total={0}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
index={selectedRowIndex}
/>
{/* 측정정보 테이블 */}
<UiTable
headName=''
tableData={rainMeasureData}
headlist={untrtSwgRainCol4}
pageSize={3}
total={totalCount}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetPageNo={isResetPage}
resetSelection={isResetSelection}
index={selectedRowIndex}
>
{/* 커스텀 헤더 */}
<TableRow>
<TableHead colSpan={5} className="text-center bg-blue-100 font-bold">수량</TableHead>
<TableHead colSpan={3} className="text-center bg-blue-100 font-bold">수질</TableHead>
</TableRow>
<TableRow>
<TableHead className="text-center">측정시작일시</TableHead>
<TableHead className="text-center">측정종료일시</TableHead>
<TableHead className="text-center">측정간격(분)</TableHead>
<TableHead className="text-center">측정기기설치위치</TableHead>
<TableHead className="text-center">유량계 종류 및 모델명</TableHead>
<TableHead className="text-center">채수시작일시</TableHead>
<TableHead className="text-center">채수종료일시</TableHead>
<TableHead className="text-center">채수횟수</TableHead>
</TableRow>
</UiTable>
</div>
<div className='border rounded-lg pt-4 bg-white'>
{/* 수량/수질측정 정보 테이블 */}
<Label className='text-2xl font-bold text-label ml-4'>수량/수질측정 정보</Label>
<hr className="my-4 ml-4 mr-4 border-t border-gray-300" />
<UiTable
headName=''
tableData={mntrngsData}
headlist={untrtSwgRainCol5}
pageSize={3}
total={totalDetailCount}
cellClick={undefined}
onPageChange={setCurDetailPage}
resetPageNo={isResetDetailPage}
/>
</div>
</div>
)
return (
<div>
{!isMainSsc && (
<VerticalTable
headName='입력정보'
tableData={featureData ? [formData] : []}
headlist={inputColumn}
pageSize={0}
total={0}
cellClick={undefined}
isEditable={true}
onPageChange={function (): void {
throw new Error('Function not implemented.');
}}
showSaveButton={true}
onSaveClick={handleSaveClick}
onFormDataChange={handleFormDataChange} // 폼 데이터 변경 콜백 추가
/>
)}
{isMainSsc && (
<div>
<div className="card-header">
<h3 className="text-2xl mx-3 font-medium text-label">중점우수토실 운영정보</h3>
</div>
<Button
className='mx-auto block w-64 mt-3 py-2 text-white rounded-sm text-sm font-medium hover:bg-blue-700 bg-blue-600'
onClick={openModal}
>
상세보기
</Button>
<UiModal
open={isModalOpen}
onOpenChange={setIsModalOpen}
bodyClass={`
h-[100vh]
w-[60vw]
max-w-none
max-h-none
overflow-y-auto
z-[101]
`}
body={renderModalBody()}
noHeaderYn='Y'
noFooterYn='Y'
/>
</div>
)}
</div>
)
}
export default Ssc