381 lines
13 KiB
TypeScript
381 lines
13 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 { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
|
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";
|
|
import { useAlertStore } from "@/shared/lib";
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
|
|
|
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 alert = useAlertStore((state) => state.setMessage);
|
|
const { confirm } = useConfirm();
|
|
//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 [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();
|
|
|
|
// 페이지 변경 시마다 데이터 fetch
|
|
useEffect(() => {
|
|
handleSearchButtonClick();
|
|
}, [curPage]);
|
|
|
|
// 조회 버튼 클릭 시 1페이지로 이동 및 데이터 초기화
|
|
const handleSearch: () => Promise<void> = async () => {
|
|
setCurPage(1); // 페이지를 1로 초기화
|
|
await handleSearchButtonClick({ page: 1 });
|
|
};
|
|
|
|
const handleSearchButtonClick = async (options?: {
|
|
page?: number;
|
|
ctpvCd?: string;
|
|
sggCd?: string;
|
|
}) => {
|
|
setIsLoadingResult(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:
|
|
(options?.ctpvCd ?? ctpvCd) !== "0"
|
|
? (options?.ctpvCd ?? ctpvCd).trim()
|
|
: null,
|
|
sggCd:
|
|
(options?.sggCd ?? sggCd) !== "0" ? (options?.sggCd ?? sggCd) : null,
|
|
swtrmtFcltNm: formValues.swtrmtFcltNm?.trim()
|
|
? formValues.swtrmtFcltNm
|
|
: null,
|
|
//auth: AUTH,
|
|
};
|
|
|
|
const queryParams = new URLSearchParams({
|
|
page: `${(options?.page ?? curPage) - 1}`,
|
|
size: "10",
|
|
sort: "rainInfwtAnlsSn,desc",
|
|
scsYn: "Y",
|
|
});
|
|
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 totalElements = Number(data?.totalElements ?? 0);
|
|
|
|
// 전체 건수를 기준으로 현재 페이지의 순번을 내림차순 계산한다.
|
|
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: Math.max(
|
|
totalElements - (((options?.page ?? curPage) - 1) * 10 + index),
|
|
0,
|
|
),
|
|
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(totalElements);
|
|
setIsLoadingResult(false);
|
|
} catch (error) {
|
|
setIsLoadingResult(false);
|
|
console.error("Error fetching analysis result data:", error);
|
|
alert("분석결과 조회 중 오류가 발생했습니다.");
|
|
setTableData([]);
|
|
setTotalData(0);
|
|
setSelectedRowData(null);
|
|
}
|
|
};
|
|
|
|
/** 검색 조건과 선택 상태를 기본값으로 되돌리고 첫 페이지를 다시 조회한다. */
|
|
const handleReset = async () => {
|
|
const now = new Date();
|
|
const currentYear = now.getFullYear();
|
|
const currentMonth = String(now.getMonth() + 1).padStart(2, "0");
|
|
form.reset({
|
|
swtrmtFcltCd: "0",
|
|
swtrmtFcltNm: "",
|
|
aplcnYmFrom: `${currentYear}01`,
|
|
aplcnYmTo: `${currentYear}${currentMonth}`,
|
|
});
|
|
setCtpvCd("0");
|
|
setSggCd("0");
|
|
setSggName("");
|
|
setCurPage(1);
|
|
await handleSearchButtonClick({ page: 1, ctpvCd: "0", sggCd: "0" });
|
|
};
|
|
|
|
// 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 () => {
|
|
if (!selectedRowData || selectedRowData.rainInfwtAnlsSn === undefined) {
|
|
alert("삭제할 파일을 선택해주세요.");
|
|
return;
|
|
}
|
|
// 삭제 확인 대화상자
|
|
const confirmDelete = await confirm("선택된 분석 결과를 삭제하시겠습니까?");
|
|
if (!confirmDelete) {
|
|
return;
|
|
}
|
|
|
|
setIsLoadingResult(true);
|
|
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),
|
|
});
|
|
|
|
// 검색 버튼 클릭 이벤트 트리거
|
|
await handleSearchButtonClick();
|
|
|
|
setIsLoadingResult(false);
|
|
|
|
alert("삭제가 완료되었습니다.");
|
|
} catch (error) {
|
|
console.error("분석결과 파일 삭제 중 오류 발생:", error);
|
|
alert("분석결과 파일 삭제 중 오류가 발생했습니다. 다시 시도해주세요.");
|
|
}
|
|
setIsLoadingResult(false);
|
|
};
|
|
|
|
return (
|
|
<BaseLayout
|
|
selectBox={{
|
|
title: "분석 대상지역 선택",
|
|
searchChk: true,
|
|
onSearchClick: handleSearch,
|
|
onResetClick: handleReset,
|
|
}}
|
|
titleName={{
|
|
mainTitle: "RDII 분석결과",
|
|
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="min-w-0 [&_.standard-data-table]:!m-0 [&_.standard-data-table]:!border-0"
|
|
>
|
|
<MapPopupDataTable
|
|
rows={tableData}
|
|
headlist={tableColumn}
|
|
title=""
|
|
total={totalData}
|
|
page={curPage}
|
|
pageSize={10}
|
|
onPageChange={setCurPage}
|
|
onRowClick={(row) => setSelectedRowData(row)}
|
|
loading={isLoadingResult}
|
|
scrollMode="horizontal"
|
|
minTableWidth="125rem"
|
|
/>
|
|
</div>,
|
|
]}
|
|
</BaseLayout>
|
|
);
|
|
};
|
|
|
|
export default Result;
|