Files
frontend/apps/st/src/components/statistics/total/SewerageAggregateSummary.tsx
T

233 lines
8.3 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import type { RealGridReact } from "realgrid-react";
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
import { statisticsSewerageAggregateSummaryApi } from "@/entities/statistics/api/statistics-sewerage-aggregate-summary-service";
import type { SewerageAggregateSummaryDownloadRequest } from "@/entities/statistics/model/statistics-total/statistics-sewerage-aggregate-summary-interface";
import { LoadingWithAnimation } from "@common/components";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { toGridRows } from "@/components/util/grid/gridFunction";
const columnInfo = {
columns: [
{
name: "statsCd",
fieldName: "statsCd",
header: { text: "통계항목코드" },
editable: false,
visible: false,
},
{ name: "tabNm", fieldName: "tabNm", header: { text: "통계항목" }, editable: false },
],
columnLayout: ["statsCd", "tabNm"],
};
type GridViewWithCheckApi = {
getCheckedRows?: (refresh?: boolean) => number[];
getCheckedItems?: (refresh?: boolean) => number[];
};
const EXCEL_DOWNLOAD_PROGRESS_MAX = 98;
const EXCEL_DOWNLOAD_PROGRESS_STEP = 1;
const EXCEL_DOWNLOAD_PROGRESS_INTERVAL_MS = 500;
const EXCEL_DOWNLOAD_PROGRESS_LABEL =
"엑셀 파일을 생성 중입니다.\n창을 닫거나 이동하지 말고 잠시만 기다려주세요.";
const SewerageAggregateSummary = () => {
const { confirm } = useConfirm();
const { setMessage: alert } = useAlertStore((state) => state);
const showGlobalProgress = useGlobalProgressStore((state) => state.show);
const hideGlobalProgress = useGlobalProgressStore((state) => state.hide);
const setGlobalProgress = useGlobalProgressStore((state) => state.setProgress);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState(false);
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const getGridRef = useRef<RealGridReact | null>(null);
const downloadProgressTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
let cancelled = false;
void (async () => {
try {
setIsLoading(true);
const response =
await statisticsSewerageAggregateSummaryApi.getAggregateSummaryTabList();
if (cancelled) return;
if (response.status === "success") {
setRowData(toGridRows(response.data ?? []));
} else {
alert(response.message || "데이터 조회 중 오류가 발생했습니다.");
}
} catch {
if (!cancelled) {
alert("데이터 조회 중 오류가 발생했습니다.");
}
} finally {
if (!cancelled) {
setIsLoading(false);
}
}
})();
return () => {
cancelled = true;
};
}, [alert]);
useEffect(() => {
return () => {
if (downloadProgressTimerRef.current !== null) {
clearInterval(downloadProgressTimerRef.current);
}
};
}, []);
const stopDownloadProgressTimer = () => {
if (downloadProgressTimerRef.current !== null) {
clearInterval(downloadProgressTimerRef.current);
downloadProgressTimerRef.current = null;
}
};
const startDownloadProgress = () => {
stopDownloadProgressTimer();
let currentProgress = 0;
showGlobalProgress({
label: EXCEL_DOWNLOAD_PROGRESS_LABEL,
progress: 0,
});
downloadProgressTimerRef.current = setInterval(() => {
currentProgress = Math.min(
currentProgress + EXCEL_DOWNLOAD_PROGRESS_STEP,
EXCEL_DOWNLOAD_PROGRESS_MAX,
);
setGlobalProgress(currentProgress);
if (currentProgress >= EXCEL_DOWNLOAD_PROGRESS_MAX) {
stopDownloadProgressTimer();
}
}, EXCEL_DOWNLOAD_PROGRESS_INTERVAL_MS);
};
const handleDownload = async (values: SearchFormValues) => {
const gridView = getGridRef.current?.gridView as GridViewWithCheckApi | undefined;
const checkedIndexes =
gridView?.getCheckedRows?.(false) ?? gridView?.getCheckedItems?.(false) ?? [];
const allRows = getGridDataRef.current?.() ?? rowData;
const statsCdList = checkedIndexes
.map((idx) => normalizeGridString(allRows[idx]?.statsCd))
.filter((cd) => cd.length > 0);
if (statsCdList.length === 0) {
alert("내려받을 통계항목을 선택해주세요.");
return;
}
if (!values.year) {
alert("통계연도를 선택해주세요.");
return;
}
if (
!(await confirm(
`선택한 데이터 및 사용자 네트워크 환경에 따라 최대 1시간까지 시간이 소요될 수 있습니다.
진행하시겠습니까?`
))
) {
return;
}
const summaryYearCnt =
values.isSummary && values.isSummary !== "ALL"
? Number(values.isSummary)
: 0;
const request: SewerageAggregateSummaryDownloadRequest = {
statsCdList,
statsYr: values.year,
ctpvNm: values.sido === "ALL" ? "" : (values.sido ?? ""),
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
summaryYearCnt: Number.isFinite(summaryYearCnt) ? summaryYearCnt : 0,
};
try {
startDownloadProgress();
const { blob, fileName } =
await statisticsSewerageAggregateSummaryApi.downloadAggregateSummary(request);
stopDownloadProgressTimer();
setGlobalProgress(100);
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName || `하수도통계 집계 요약표_${values.year}.xlsx`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
await new Promise((resolve) => setTimeout(resolve, 400));
alert("저장되었습니다.");
} catch (error) {
const message =
error instanceof Error && error.message
? error.message
: "엑셀 내려받기 중 오류가 발생했습니다.";
alert(message);
} finally {
stopDownloadProgressTimer();
hideGlobalProgress();
}
};
return (
<div className="relative">
<SearchForm
isSummary={true}
sidoSelectText="전체"
searchButtonText="내려받기"
onSubmit={(values) => void handleDownload(values)}
isReset={false}
/>
{isLoading && (
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
<DataGrid
columnInfo={columnInfo}
rowStartNum={1}
isExcelImport={false}
isExcelExport={false}
showHeaderCheckbox
rowData={rowData}
setRowData={setRowData}
getGridDataRef={getGridDataRef}
getGridRef={getGridRef}
/>
</div>
);
};
function normalizeGridString(value: unknown): string {
if (value === null || value === undefined) return "";
return typeof value === "string" ? value : String(value);
}
export default SewerageAggregateSummary;