Files
frontend/apps/sn/app/(with-base-layout)/ii/rdiiResult/page.tsx
T

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;