Files
frontend/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx
T
swcho cb1bcd6061 feat:
- searchModal 화면 반응형 크기 수정
- 침입수 분석 결과, RDII 분석 결과 표 세로 스크롤 삭제
2025-12-19 18:06:47 +09:00

328 lines
12 KiB
TypeScript

/**
* ➡️ 경로 : I/I 분석 - RDII 분석결과
*
* 📄 RDII Result Page
* RDII 분석 결과 페이지 컴포넌트
*
* - RDII 분석 결과 조회 및 테이블 표시
* - 페이징 및 검색 기능 (시도/시군구/하수처리시설/기간 기반)
* - 분석 결과 선택 및 엑셀 다운로드 기능
* - 분석 결과 파일 삭제 기능
* - 로딩 오버레이 및 에러 핸들링 지원
* - Zustand를 통한 상태 관리 및 데이터 포맷팅
*
* 🔗 사용 API
* - RDII분석 목록 조회: GET /api/v1/rdiiAnls (sewerage-service)
* - RDII분석결과관로 목록 조회: GET /api/v1/rdiiAnlsRslt/ppl/{rainInfwtAnlsSn} (sewerage-service)
* - RDII분석결과 삭제: DELETE /api/v1/rdiiAnlsRslt/{rainInfwtAnlsSn} (sewerage-service)
* - RDII분석 삭제: DELETE /api/v1/rdiiAnls/{rainInfwtAnlsSn} (sewerage-service)
*/
'use client'
import { deleteData, getData } from '@/components/lib/api'
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import BaseLayout from '../BaseLayout'
import { handleDelete } from '@/components/lib/utils'
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import useStore from '@/store/useStore';
const formSchema = z.object({
swtrmtFcltCd: z.string().default('0'), // 하수처리시설코드
swtrmtFcltNm: z.string().default(''), // 하수처리시설명
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
const now = new Date();
const year = now.getFullYear();
// const month = String(now.getMonth() + 1).padStart(2, '0');
// return `${year}${month}`;
return `${year}01`; // 기본값을 1월로 설정
}), // 분석기간 시작연월
aplcnYmTo: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return `${year}${month}`;
}), // 분석기간 종료연월
})
type ResultFormValues = z.infer<typeof formSchema>
const tableColumn = [
{ id: 'rowNumber', title: '일련번호' },
{ id: 'ctpvNm', title: '시도' },
{ id: 'sggNm', title: '시군구' },
{ id: 'swtrmtFcltNm', title: '하수처리시설' },
{ id: 'aplcnYm', title: '상수사용연월' },
{ id: 'drtwSwtcRt', title: '오수 전환율' },
{ id: 'srcNm', title: 'RTK 계수 출처' },
{ id: 'cntbArea', title: 'RDII 기여면적' },
{ id: 'anlsInfo', title: 'RDII 분석정보' },
{ id: 'regDt', title: '분석일자' },
{ id: 'rgtrNm', title: '분석자' },
{ id: 'scsYn', title: '분석성공여부' }
]
const Result = () => {
//const AUTH = useCookieAuth();
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
swtrmtFcltCd: '0',
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
const now = new Date();
const year = now.getFullYear();
// const month = String(now.getMonth() + 1).padStart(2, '0');
// return `${year}${month}`;
return `${year}01`;
})(),
aplcnYmTo: (() => { // 현재 연월을 yyyyMM 형식으로 설정
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return `${year}${month}`;
})(),
}
})
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [selectedRowData, setSelectedRowData] = useState<any>(null);
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
const [ctpvCd, setCtpvCd] = useState<string>('0'); // 시도 코드 상태
const [sggCd, setSggCd] = useState<string>('0'); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
const { ctpv, sgg } = useStore();
useEffect(() => {
// setCurPage(1);
handleSearchButtonClick();
}, [])
// 페이지 변경 시마다 데이터 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 = form.getValues(); // react-hook-form에서 현재 값 가져오기
const inftwtAnlsSearchDto = {
aplcnYmFrom: formValues.aplcnYmFrom?.trim() ? formValues.aplcnYmFrom : null,
aplcnYmTo: formValues.aplcnYmTo?.trim() ? formValues.aplcnYmTo : null,
ctpvCd: ctpvCd !== '0' ? ctpvCd.trim() : null,
sggCd: sggCd !== '0' ? sggCd : null,
swtrmtFcltNm: formValues.swtrmtFcltNm?.trim() ? formValues.swtrmtFcltNm : null,
//auth: AUTH,
};
const queryParams = new URLSearchParams({
page: `${curPage - 1}`,
size: '10',
sort: 'rainInfwtAnlsSn,desc'
});
Object.entries(inftwtAnlsSearchDto).forEach(([key, value]) => {
if (value !== undefined && value !== null && String(value).trim() !== '') { // undefined, null, 빈 문자열이 아닌 경우에만 추가
queryParams.append(key, String(value));
}
});
const queryString = queryParams.toString();
try {
const data = await getData(`v1/rdiiAnls${queryString ? `?${queryString}` : ''}`);
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
const dataWithRowNumber = (data.content || []).map((item: any, index: number) => {
// 🎯 먼저 각 아이템을 포맷팅
const formattedItem = formatDisplayData(item);
const ctpvOne = ctpv.find(code => code.val.trim() === item?.ctpvCd?.trim());
const sggOne = sgg.find(code => code.val === item.sggCd);
// 추가 처리 및 일련번호 추가
return {
...formattedItem,
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn, // scsYn이 null이면 'N'으로 변경
ctpvNm: ctpvOne ? ctpvOne.text.trim() : item?.ctpvCd?.trim(),
sggNm: sggOne ? sggOne.text : item.sggCd,
};
})
.filter((item: any) => item.scsYn === 'Y');
// 선택된 행 초기화
setSelectedRowData(null);
setTableData(dataWithRowNumber);
setTotalData(dataWithRowNumber.length);
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Error fetching analysis result data:", error);
alert("분석결과 조회 중 오류가 발생했습니다.");
setTableData([]);
setTotalData(0);
setSelectedRowData(null);
}
setResetSelection(false);
};
// 테이블 셀 클릭 핸들러 추가
const handleCellClick = (index: number | undefined, headId: string) => {
if (index !== undefined && tableData[index]) {
setSelectedRowData(tableData[index]);
}
};
// Excel 다운로드 핸들러 추가
const handleExcelDownload = async () => {
setIsLoadingResult(true);
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
setIsLoadingResult(false);
alert('다운로드할 데이터를 선택해주세요.');
return;
}
try {
const data = await getData(`v1/rdiiAnlsRslt/ppl/${selectedRowData.rainInfwtAnlsSn}`);
// 선택된 행 데이터를 Excel 다운로드 형식으로 변환
const excelData = data.content; // 배열로 감싸서 전달
// 헤더 매핑 정의
const headerMapping: { [key: string]: string } = {
'rainInfwtAnlsSn': 'RDII분석\n일련번호',
'pplMngNo': '관로\n관리번호',
'anlsTm': '분석시각',
'predcOilq': '예측유량\n(m³/h)'
};
// 파일명 생성 (분석번호 기반)
const fileName = `RDII분석결과_${selectedRowData.swtrmtFcltNm}_${selectedRowData.aplcnYm}_${selectedRowData.rainInfwtAnlsSn}`;
await downloadExcelWithKoreanHeaders({
data: excelData as JSON[],
fileName: fileName,
sheetNameArray: ["RDII 분석결과"],
headerRange: "A1:D1",
headerMapping: headerMapping
});
setIsLoadingResult(false);
} catch (error) {
setIsLoadingResult(false);
console.error("Excel 다운로드 중 오류 발생:", error);
alert("Excel 다운로드 중 오류가 발생했습니다. 다시 시도해주세요.");
}
};
// 파일 삭제 핸들러 추가
const handleFileDelete = async () => {
setIsLoadingResult(true);
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
alert('삭제할 파일을 선택해주세요.');
return;
}
// 삭제 확인 대화상자
const confirmDelete = window.confirm('선택된 분석 결과를 삭제하시겠습니까?');
if (!confirmDelete) {
return;
}
try {
// 삭제 API 호출
await deleteData(`v1/rdiiAnlsRslt/${selectedRowData.rainInfwtAnlsSn}`);
await deleteData(`v1/rdiiAnls/${selectedRowData.rainInfwtAnlsSn}`);
// 첨부파일(inp파일) 삭제 API 호출
handleDelete(selectedRowData.atflCd, {
handleDeleteStart: () => console.log("파일 삭제 시작"),
handleDeleteSuccess: (response) => console.log("파일 삭제 성공:", response),
handleDeleteError: (error) => console.error("파일 삭제 실패:", error),
});
// 테이블 선택 상태 초기화 트리거
setResetSelection(true);
// 검색 버튼 클릭 이벤트 트리거
await handleSearchButtonClick();
setIsLoadingResult(false);
alert('삭제가 완료되었습니다.');
setTimeout(() => {
setResetSelection(false);
}, 100);
} catch (error) {
console.error("분석결과 파일 삭제 중 오류 발생:", error);
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
}
setIsLoadingResult(false);
};
return (
<BaseLayout
selectBox={{ title: '분석 대상지역 선택', searchChk: true, onSearchClick: handleSearch }}
titleName={{ mainTitle: '분석결과', firstCardTitle: 'RDII 산정결과 현황' }}
form={form} // form 인스턴스 전달
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwagePrcsFclt={form.watch("swtrmtFcltCd")}
onSwagePrcsFcltChange={(val) => form.setValue("swtrmtFcltCd", String(val))}
aplcnYmFrom={form.watch("aplcnYmFrom")}
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
aplcnYmTo={form.watch("aplcnYmTo")}
setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
setSwtrmtFcltNm={(val: string) => { form.setValue("swtrmtFcltNm", val) }}
setIsLoading={setIsLoadingResult}
// Excel 다운로드 핸들러 추가
onExcelDownload={handleExcelDownload}
onDeleteFile={handleFileDelete}
selectedRowData={selectedRowData}
>
{[<div key='table1' className="flex-1 flex flex-col border rounded-lg bg-white shadow-md min-h-0">
{/* <div className="mb-[1.2rem] text-xl font-medium">○ RDII 분석결과 현황</div> */}
<div className="flex-1 min-h-0">
<UiTable
headName=''
headlist={tableColumn}
tableData={tableData}
total={totalData}
pageSize={10}
cellClick={handleCellClick}
onPageChange={setCurPage}
resetSelection={resetSelection}
resetPageNo={isResetPage}
/>
</div>
</div>
]}
</BaseLayout>
)
}
export default Result