From a274b5a26157b8583b403d0eb4adf27413ea1160 Mon Sep 17 00:00:00 2001 From: kiminchan Date: Tue, 9 Jun 2026 15:26:09 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A7=80=EB=B0=98=EC=B9=A8=ED=95=98=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20=EC=A1=B0=ED=9A=8C=EC=A1=B0=EA=B1=B4,=20?= =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C,=20=EC=A7=80=EB=8F=84?= =?UTF-8?q?=ED=91=9C=EC=B6=9C=20=EB=B0=A9=EC=8B=9D=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/sinkhole/SinkholePopupPage.tsx | 111 ++++++- .../map/components/sinkhole/sinkholeTypes.ts | 75 ++++- apps/sn/app/(map-layout)/map/page.tsx | 295 +++++++++++++++--- .../001-qa-SFR-010-010-지반침하정보-통합검증계획.md | 2 +- .../002-qa-SFR-010-010-지반침하정보-통합검증결과.md | 46 +++ .../001-fe-SFR-010-010-지반침하정보-작업카드.md | 7 +- .../001-fe-SFR-010-010-지반침하정보-통합구현기록.md | 90 ++++++ .../map/layericon/SN_SINKHOLE_SPOT_BLUE.svg | 3 + .../map/layericon/SN_SINKHOLE_SPOT_ORANGE.svg | 3 + .../business_components/src/ui/map/GisTree.js | 13 +- 10 files changed, 567 insertions(+), 78 deletions(-) create mode 100644 apps/sn/public/images/map/layericon/SN_SINKHOLE_SPOT_BLUE.svg create mode 100644 apps/sn/public/images/map/layericon/SN_SINKHOLE_SPOT_ORANGE.svg diff --git a/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholePopupPage.tsx b/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholePopupPage.tsx index 19657d6f0..a8db43e10 100644 --- a/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholePopupPage.tsx +++ b/apps/sn/app/(map-layout)/map/components/sinkhole/SinkholePopupPage.tsx @@ -14,8 +14,10 @@ import { formatDateInputToYmd, formatYmdToDateInput, normalizeSinkholeFilters, - SINKHOLE_EXCEL_HEADERS, + SINKHOLE_COORD_STATUS_OPTIONS, + SINKHOLE_DOWNLOAD_EXCEL_HEADERS, SINKHOLE_FILTER_SYNC_MESSAGE, + SINKHOLE_MIN_SEARCH_YMD, SINKHOLE_RESTORE_STATUS_OPTIONS, SINKHOLE_ROW_SELECT_MESSAGE, type SinkholeFilters, @@ -23,6 +25,7 @@ import { } from "./sinkholeTypes"; const PAGE_SIZE = 20; +const DOWNLOAD_BATCH_SIZE = 2000; type SinkholeTableRow = SinkholeListItem & { rowNo: number; @@ -60,10 +63,10 @@ const toCoordStatusDisplay = (value: unknown) => { return normalized || "-"; }; -const toTableRows = (items: SinkholeListItem[], page: number) => +const toTableRows = (items: SinkholeListItem[], page: number, pageSize = PAGE_SIZE) => items.map((item, index) => ({ ...item, - rowNo: page * PAGE_SIZE + index + 1, + rowNo: page * pageSize + index + 1, sizeWidthDisplay: toDisplay(item.sizeWidth), sizeLengthDisplay: toDisplay(item.sizeLength), sizeDepthDisplay: toDisplay(item.sizeDepth), @@ -82,7 +85,6 @@ const buildDownloadRows = (rows: SinkholeTableRow[]) => sizeDepth: row.sizeDepthDisplay, detailCause: row.detailCauseDisplay, restoreStatus: row.restoreStatus || "-", - coordStatus: row.coordStatusDisplay, restoreCompletedYmd: row.restoreCompletedYmdDisplay, })); @@ -100,6 +102,25 @@ export default function SinkholePopupPage() { ctpvCd: searchParams.get("ctpvCd") ?? "0", sggCd: searchParams.get("sggCd") ?? "0", restoreStatus: searchParams.get("restoreStatus") ?? "0", + coordStatus: searchParams.get("coordStatus") ?? "0", + }, + undefined, + ); + + if (!seeded.startYmd || !seeded.endYmd) { + return normalizeSinkholeFilters(buildSinkholeDefaultFilters(), undefined); + } + return seeded; + }); + const [appliedFilters, setAppliedFilters] = useState(() => { + const seeded = normalizeSinkholeFilters( + { + startYmd: searchParams.get("startYmd") ?? "", + endYmd: searchParams.get("endYmd") ?? "", + ctpvCd: searchParams.get("ctpvCd") ?? "0", + sggCd: searchParams.get("sggCd") ?? "0", + restoreStatus: searchParams.get("restoreStatus") ?? "0", + coordStatus: searchParams.get("coordStatus") ?? "0", }, undefined, ); @@ -128,13 +149,28 @@ export default function SinkholePopupPage() { }, [ctpv.length, fetchCtpv, fetchSgg, sgg.length]); const fetchRows = useCallback( - async (page: number, nextFilters: SinkholeFilters) => { - const queryString = buildSinkholeListQueryString(nextFilters, ctpv as SelectDataType[], sgg as SelectDataType[], page - 1, PAGE_SIZE); + async ( + page: number, + nextFilters: SinkholeFilters, + pageSize = PAGE_SIZE, + options?: { + includeCoordStatus?: boolean; + }, + ) => { + const queryString = buildSinkholeListQueryString( + nextFilters, + ctpv as SelectDataType[], + sgg as SelectDataType[], + page - 1, + pageSize, + options, + ); const response = await getRawJson(`/sewerage-service/api/v1/sinkholes?${queryString}`); const payload = unwrapPayload<{ items?: SinkholeListItem[]; total?: number }>(response); const items = Array.isArray(payload?.items) ? payload.items : []; return { - rows: toTableRows(items, page - 1), + rows: toTableRows(items, page - 1, pageSize), + items, total: Number(payload?.total ?? 0), }; }, @@ -150,6 +186,7 @@ export default function SinkholePopupPage() { setRows(response.rows); setTotal(response.total); setCurrentPage(page); + setAppliedFilters(normalized); window.opener?.postMessage( { type: SINKHOLE_FILTER_SYNC_MESSAGE, @@ -197,18 +234,43 @@ export default function SinkholePopupPage() { }; const handleDownload = async () => { - if (rows.length === 0) { + if (total === 0) { alert("다운로드할 데이터가 없습니다."); return; } - await downloadExcelWithKoreanHeaders({ - data: buildDownloadRows(rows) as unknown as JSON[], - fileName: `지반침하정보_${new Date().toISOString().slice(0, 10)}.xlsx`, - sheetNameArray: ["지반침하정보"], - headerRange: "A1:J2", - headerMapping: SINKHOLE_EXCEL_HEADERS, - }); + setIsLoading(true); + try { + const normalized = normalizeSinkholeFilters(appliedFilters); + const downloadItems: SinkholeListItem[] = []; + const totalPages = Math.max(1, Math.ceil(total / DOWNLOAD_BATCH_SIZE)); + + for (let page = 1; page <= totalPages; page += 1) { + const response = await fetchRows(page, normalized, DOWNLOAD_BATCH_SIZE, { + includeCoordStatus: false, + }); + downloadItems.push(...response.items); + } + + if (downloadItems.length === 0) { + alert("다운로드할 데이터가 없습니다."); + return; + } + + const downloadRows = toTableRows(downloadItems, 0, 1); + await downloadExcelWithKoreanHeaders({ + data: buildDownloadRows(downloadRows) as unknown as JSON[], + fileName: `지반침하정보_${new Date().toISOString().slice(0, 10)}.xlsx`, + sheetNameArray: ["지반침하정보"], + headerRange: "A1:I1", + headerMapping: SINKHOLE_DOWNLOAD_EXCEL_HEADERS, + }); + } catch (error) { + console.error("지반침하 다운로드 중 오류:", error); + alert(error instanceof Error ? error.message : "지반침하 다운로드 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } }; return ( @@ -220,12 +282,13 @@ export default function SinkholePopupPage() {
-
+
+ +