sn: appendApiSearchParams() 함수 적용
This commit is contained in:
@@ -13,6 +13,7 @@ import { useAlertStore } from "@/shared/lib";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox";
|
||||
import { formatDisplayData } from "@/components/lib/utils/formatDisplayValue";
|
||||
import useStore from "@/store/useStore";
|
||||
@@ -132,16 +133,13 @@ export default function InftwtResultPopupPage() {
|
||||
: "inftwtAnlsSn,desc",
|
||||
);
|
||||
|
||||
if (nextFilters.aplcnYmFrom)
|
||||
queryParams.set("aplcnYmFrom", nextFilters.aplcnYmFrom);
|
||||
if (nextFilters.aplcnYmTo)
|
||||
queryParams.set("aplcnYmTo", nextFilters.aplcnYmTo);
|
||||
if (nextFilters.ctpvCd !== "0")
|
||||
queryParams.set("ctpvCd", nextFilters.ctpvCd);
|
||||
if (nextFilters.sggCd !== "0")
|
||||
queryParams.set("sggCd", nextFilters.sggCd);
|
||||
if (nextFilters.swtrmtFcltNm.trim())
|
||||
queryParams.set("swtrmtFcltNm", nextFilters.swtrmtFcltNm.trim());
|
||||
appendApiSearchParams(queryParams, {
|
||||
aplcnYmFrom: nextFilters.aplcnYmFrom,
|
||||
aplcnYmTo: nextFilters.aplcnYmTo,
|
||||
ctpvCd: nextFilters.ctpvCd,
|
||||
sggCd: nextFilters.sggCd,
|
||||
swtrmtFcltNm: nextFilters.swtrmtFcltNm,
|
||||
});
|
||||
|
||||
const response = await getData(`v1/inftwtAnls?${queryParams.toString()}`);
|
||||
const content = Array.isArray(response?.content) ? response.content : [];
|
||||
|
||||
+11
-13
@@ -6,6 +6,7 @@ import { Button, Input } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/store/useStore";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
@@ -131,19 +132,16 @@ export default function PipeAnalysisDetailPopupPage() {
|
||||
size: String(size),
|
||||
});
|
||||
|
||||
if (nextFilters.ctpvCd !== "0") params.set("ctpvCd", nextFilters.ctpvCd);
|
||||
if (nextFilters.sggCd !== "0") params.set("sggCd", nextFilters.sggCd);
|
||||
if (nextFilters.swtrmtZoneNm !== "0")
|
||||
params.set("swtrmtZoneNm", nextFilters.swtrmtZoneNm);
|
||||
if (baseElapsedYearBucket)
|
||||
params.set("elapsedYearBucket", baseElapsedYearBucket);
|
||||
if (nextFilters.materialCode !== "0")
|
||||
params.set("materialCode", nextFilters.materialCode);
|
||||
if (nextFilters.useCd !== "0") params.set("useCd", nextFilters.useCd);
|
||||
if (nextFilters.diameterFrom)
|
||||
params.set("diameterFrom", nextFilters.diameterFrom);
|
||||
if (nextFilters.diameterTo)
|
||||
params.set("diameterTo", nextFilters.diameterTo);
|
||||
appendApiSearchParams(params, {
|
||||
ctpvCd: nextFilters.ctpvCd,
|
||||
sggCd: nextFilters.sggCd,
|
||||
swtrmtZoneNm: nextFilters.swtrmtZoneNm,
|
||||
elapsedYearBucket: baseElapsedYearBucket,
|
||||
materialCode: nextFilters.materialCode,
|
||||
useCd: nextFilters.useCd,
|
||||
diameterFrom: nextFilters.diameterFrom,
|
||||
diameterTo: nextFilters.diameterTo,
|
||||
});
|
||||
if (scope.regionCode) params.set("scopeRegionCode", scope.regionCode);
|
||||
|
||||
const response = await getData(
|
||||
|
||||
@@ -14,6 +14,7 @@ import { StandardPopupLayout } from "@common/business_components";
|
||||
import UiPageNations from "@common/business_components/ui/table/uiPageNations";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/store/useStore";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
@@ -144,10 +145,11 @@ export default function PipeAnalysisPopupPage() {
|
||||
analysisType: nextFilters.analysisType,
|
||||
});
|
||||
|
||||
if (nextFilters.ctpvCd !== "0") params.set("ctpvCd", nextFilters.ctpvCd);
|
||||
if (nextFilters.sggCd !== "0") params.set("sggCd", nextFilters.sggCd);
|
||||
if (nextFilters.swtrmtZoneNm !== "0")
|
||||
params.set("swtrmtZoneNm", nextFilters.swtrmtZoneNm);
|
||||
appendApiSearchParams(params, {
|
||||
ctpvCd: nextFilters.ctpvCd,
|
||||
sggCd: nextFilters.sggCd,
|
||||
swtrmtZoneNm: nextFilters.swtrmtZoneNm,
|
||||
});
|
||||
if (nextFilters.scopeRegionCode)
|
||||
params.set("scopeRegionCode", nextFilters.scopeRegionCode);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
@@ -134,16 +135,13 @@ export default function RdiiResultPopupPage() {
|
||||
: "rainInfwtAnlsSn,desc",
|
||||
);
|
||||
|
||||
if (nextFilters.aplcnYmFrom)
|
||||
queryParams.set("aplcnYmFrom", nextFilters.aplcnYmFrom);
|
||||
if (nextFilters.aplcnYmTo)
|
||||
queryParams.set("aplcnYmTo", nextFilters.aplcnYmTo);
|
||||
if (nextFilters.ctpvCd !== "0")
|
||||
queryParams.set("ctpvCd", nextFilters.ctpvCd);
|
||||
if (nextFilters.sggCd !== "0")
|
||||
queryParams.set("sggCd", nextFilters.sggCd);
|
||||
if (nextFilters.swtrmtFcltNm.trim())
|
||||
queryParams.set("swtrmtFcltNm", nextFilters.swtrmtFcltNm.trim());
|
||||
appendApiSearchParams(queryParams, {
|
||||
aplcnYmFrom: nextFilters.aplcnYmFrom,
|
||||
aplcnYmTo: nextFilters.aplcnYmTo,
|
||||
ctpvCd: nextFilters.ctpvCd,
|
||||
sggCd: nextFilters.sggCd,
|
||||
swtrmtFcltNm: nextFilters.swtrmtFcltNm,
|
||||
});
|
||||
|
||||
const response = await getData(`v1/rdiiAnls?${queryParams.toString()}`);
|
||||
const content = Array.isArray(response?.content) ? response.content : [];
|
||||
|
||||
@@ -69,3 +69,11 @@
|
||||
- 2026-08-18 목록 강수량을 소수점 한 자리로, 헤더를 `초과빈도년도`로 변경하고 값의 `년` 접미사를 제거했다.
|
||||
- 기존 `.next` 산출물을 재생성한 뒤 `pnpm --filter sn run build`를 최종 통과했다.
|
||||
- TypeScript 정적검사는 저장소 기존 `ignoreDeprecations` 설정과 실행 중 산출물 잠금 때문에 완료하지 못했고, Next 빌드는 최적화 단계 장기 대기 후 중단했다.
|
||||
|
||||
## Excel 프론트 생성 전환 결과 (2026-09-03)
|
||||
|
||||
- 현재 적용 검색조건으로 `/api/v1/fldng/predc/prst`를 500건 단위 전체 페이지 조회한다.
|
||||
- 공통 `fetchAllPages`로 `totalElements`와 실제 수집 건수 일치를 검증한다.
|
||||
- `fldng-predc-prst-excel.ts`가 화면 열 순서의 단일 시트 `침수예측현황.xlsx`를 브라우저에서 생성한다.
|
||||
- 서버 `/prst/excel` Blob 호출과 `getBlobData`, `fileDownload` 사용을 제거했다.
|
||||
- 독립 TypeScript `--noEmit` 검증을 통과했다.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
|
||||
export type FloodPredictionExcelRow = {
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
analysisDateTime: string;
|
||||
rainBeginningDateTime: string;
|
||||
observedAccumulatedRainfall: number | null;
|
||||
predictionHour: number | null;
|
||||
predictedAccumulatedRainfall: number | null;
|
||||
exceededFrequencyYear: number | null;
|
||||
floodRiskGrade: string;
|
||||
};
|
||||
|
||||
const HEADERS: Record<string, string> = {
|
||||
rowNo: "순번",
|
||||
ctpvNm: "시도",
|
||||
sggNm: "시군구",
|
||||
analysisDateTime: "분석시각",
|
||||
rainBeginningDateTime: "강우 시작시각",
|
||||
observedAccumulatedRainfall: "관측 누적강수량(mm)",
|
||||
predictionHour: "예측시간",
|
||||
predictedAccumulatedRainfall: "예측 누적강수량(mm)",
|
||||
exceededFrequencyYear: "초과빈도(년)",
|
||||
floodRiskGrade: "침수위험도",
|
||||
};
|
||||
|
||||
/** yyyyMMddHHmm 형식의 API 시각을 Excel 표시 문자열로 변환한다. */
|
||||
const formatDateTime = (value: string) =>
|
||||
/^\d{12}$/.test(value)
|
||||
? `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)} ${value.slice(8, 10)}:${value.slice(10, 12)}`
|
||||
: value || "-";
|
||||
|
||||
/** 적용 조회조건의 전체 침수예측현황을 브라우저에서 한 시트 XLSX로 생성한다. */
|
||||
export const downloadFloodPredictionStatusExcel = async (
|
||||
rows: FloodPredictionExcelRow[],
|
||||
) => {
|
||||
const excelRows = rows.map((row, index) => ({
|
||||
rowNo: index + 1,
|
||||
ctpvNm: row.ctpvNm || "-",
|
||||
sggNm: row.sggNm || "-",
|
||||
analysisDateTime: formatDateTime(row.analysisDateTime),
|
||||
rainBeginningDateTime: formatDateTime(row.rainBeginningDateTime),
|
||||
observedAccumulatedRainfall:
|
||||
row.observedAccumulatedRainfall == null
|
||||
? "-"
|
||||
: Number(row.observedAccumulatedRainfall.toFixed(1)),
|
||||
predictionHour:
|
||||
row.predictionHour == null ? "-" : `${row.predictionHour}시간 후`,
|
||||
predictedAccumulatedRainfall:
|
||||
row.predictedAccumulatedRainfall == null
|
||||
? "-"
|
||||
: Number(row.predictedAccumulatedRainfall.toFixed(1)),
|
||||
exceededFrequencyYear:
|
||||
row.exceededFrequencyYear == null ? "-" : row.exceededFrequencyYear,
|
||||
floodRiskGrade: row.floodRiskGrade || "-",
|
||||
}));
|
||||
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: excelRows as unknown as JSON[],
|
||||
fileName: "침수예측현황.xlsx",
|
||||
sheetNameArray: ["침수예측현황"],
|
||||
headerRange: "A1:J1",
|
||||
headerMapping: HEADERS,
|
||||
});
|
||||
};
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from "@common/business_components";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getBlobData, getData, getRawJson } from "@/components/lib/api";
|
||||
import { fileDownload } from "@/components/lib/utils/fileDownload";
|
||||
import { getData, getRawJson } from "@/components/lib/api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
@@ -18,6 +18,8 @@ import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import useStore from "@/store/useStore";
|
||||
import { createPredictionAnalysisTimeRange } from "../model/analysisTimeRange";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import { downloadFloodPredictionStatusExcel } from "../lib/fldng-predc-prst-excel";
|
||||
|
||||
type ApiRow = {
|
||||
sggCd: string;
|
||||
@@ -55,6 +57,7 @@ type Filters = {
|
||||
};
|
||||
|
||||
const PAGE_SIZE = 10;
|
||||
const EXCEL_DOWNLOAD_PAGE_SIZE = 500;
|
||||
const MAP_FREQUENCIES = new Set([30, 50, 80, 100]);
|
||||
const FREQUENCY_OPTIONS = [30, 50, 80, 100] as const;
|
||||
|
||||
@@ -118,7 +121,7 @@ const buildInitialFilters = (): Filters => {
|
||||
rainBgngTo: "",
|
||||
analysisFrom: toDateTimeLocal(range.analysisFrom),
|
||||
analysisTo: toDateTimeLocal(range.analysisTo),
|
||||
frequencyYear: "",
|
||||
frequencyYear: "0",
|
||||
};
|
||||
};
|
||||
|
||||
@@ -141,21 +144,21 @@ const formatNumber = (value: number | null, digits: number) =>
|
||||
: Number(value).toFixed(digits);
|
||||
|
||||
/** 화면 조회조건을 목록·Excel API 쿼리로 변환한다. */
|
||||
const buildQuery = (filters: Filters, page?: number) => {
|
||||
const buildQuery = (filters: Filters, page?: number, size = PAGE_SIZE) => {
|
||||
const params = new URLSearchParams();
|
||||
if (page !== undefined) {
|
||||
params.set("page", String(page - 1));
|
||||
params.set("size", String(PAGE_SIZE));
|
||||
params.set("size", String(size));
|
||||
}
|
||||
if (filters.ctpvCd !== "0") params.set("ctpvCd", filters.ctpvCd.slice(0, 2));
|
||||
if (filters.sggCd !== "0") params.set("sggCd", filters.sggCd.slice(0, 5));
|
||||
(
|
||||
["rainBgngFrom", "rainBgngTo", "analysisFrom", "analysisTo"] as const
|
||||
).forEach((key) => {
|
||||
const value = apiDate(filters[key]);
|
||||
if (value) params.set(key, value);
|
||||
appendApiSearchParams(params, {
|
||||
ctpvCd: filters.ctpvCd === "0" ? "0" : filters.ctpvCd.slice(0, 2),
|
||||
sggCd: filters.sggCd === "0" ? "0" : filters.sggCd.slice(0, 5),
|
||||
rainBgngFrom: apiDate(filters.rainBgngFrom),
|
||||
rainBgngTo: apiDate(filters.rainBgngTo),
|
||||
analysisFrom: apiDate(filters.analysisFrom),
|
||||
analysisTo: apiDate(filters.analysisTo),
|
||||
frequencyYear: filters.frequencyYear,
|
||||
});
|
||||
if (filters.frequencyYear) params.set("frequencyYear", filters.frequencyYear);
|
||||
return params.toString();
|
||||
};
|
||||
|
||||
@@ -176,7 +179,7 @@ const validateFilters = (filters: Filters) => {
|
||||
return "분석시각의 시작은 종료보다 늦을 수 없습니다.";
|
||||
}
|
||||
if (
|
||||
filters.frequencyYear &&
|
||||
filters.frequencyYear != "0" &&
|
||||
!FREQUENCY_OPTIONS.includes(
|
||||
Number(filters.frequencyYear) as 30 | 50 | 80 | 100,
|
||||
)
|
||||
@@ -389,10 +392,25 @@ export default function FldngPredcPrstPopupContent() {
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const blob = await getBlobData(
|
||||
`/sewerage-service/api/v1/fldng/predc/prst/excel?${buildQuery(appliedFilters)}`,
|
||||
);
|
||||
fileDownload(blob, "침수예측현황.xlsx");
|
||||
const result = await fetchAllPages<
|
||||
{ content: ApiRow[]; totalElements: number },
|
||||
ApiRow
|
||||
>({
|
||||
batchSize: EXCEL_DOWNLOAD_PAGE_SIZE,
|
||||
fetchPage: async (pageIndex, size) => {
|
||||
const response = await getRawJson(
|
||||
`/sewerage-service/api/v1/fldng/predc/prst?${buildQuery(appliedFilters, pageIndex + 1, size)}`,
|
||||
);
|
||||
const payload = response?.data ?? response;
|
||||
return {
|
||||
content: Array.isArray(payload?.content) ? payload.content : [],
|
||||
totalElements: Number(payload?.totalElements ?? 0),
|
||||
};
|
||||
},
|
||||
getItems: (response) => response.content,
|
||||
getTotal: (response) => response.totalElements,
|
||||
});
|
||||
await downloadFloodPredictionStatusExcel(result.items);
|
||||
} catch (error) {
|
||||
console.error("침수예측현황 Excel 다운로드 중 오류:", error);
|
||||
alert("침수예측현황 Excel 다운로드에 실패했습니다.");
|
||||
@@ -519,7 +537,7 @@ export default function FldngPredcPrstPopupContent() {
|
||||
}))
|
||||
}
|
||||
>
|
||||
<option value="">전체</option>
|
||||
<option value="0">전체</option>
|
||||
{FREQUENCY_OPTIONS.map((frequency) => (
|
||||
<option key={frequency} value={String(frequency)}>
|
||||
{frequency}년
|
||||
|
||||
Reference in New Issue
Block a user