127 lines
4.5 KiB
TypeScript
127 lines
4.5 KiB
TypeScript
/** 지반침하 상세의 표시값과 다운로드 시점 관로 조회 결과를 두 시트로 저장한다. */
|
|
import { getRawJson } from "@/shared/api/sewerage-api";
|
|
import {
|
|
downloadExcelWorkbook,
|
|
type ExcelCellValue,
|
|
type ExcelSheetSpec,
|
|
} from "@/shared/util/download-excel-workbook";
|
|
import type { SinkholeDetail, SinkholeNearbyPipe } from "./sinkhole-types";
|
|
|
|
type DownloadOptions = {
|
|
detail: SinkholeDetail;
|
|
radiusMeters: number;
|
|
summaryEntries: Array<[string, string]>;
|
|
getPipeUseName: (pipe: SinkholeNearbyPipe) => string;
|
|
};
|
|
|
|
/** 식별자는 문자열을 유지하고 결측 숫자는 0으로 바꾸지 않는다. */
|
|
const numberCell = (value: unknown): ExcelCellValue => {
|
|
if (value == null || String(value).trim() === "") return "-";
|
|
const number = Number(value);
|
|
return Number.isFinite(number) ? number : String(value);
|
|
};
|
|
|
|
/** 화면과 같은 순서의 발생정보 및 반경 내 관로 목록 시트 명세를 만든다. */
|
|
export const buildSinkholeDetailSheets = (
|
|
options: DownloadOptions,
|
|
pipes: SinkholeNearbyPipe[],
|
|
noCoordinates: boolean,
|
|
): ExcelSheetSpec[] => {
|
|
const pipeRows: ExcelCellValue[][] = pipes.map((pipe, index) => [
|
|
index + 1,
|
|
String(pipe.pipeId ?? "").trim() || "-",
|
|
options.getPipeUseName(pipe),
|
|
numberCell(pipe.elapsedYears),
|
|
numberCell(pipe.distanceMeters),
|
|
]);
|
|
if (pipeRows.length === 0)
|
|
pipeRows.push([
|
|
noCoordinates
|
|
? "좌표가 없어 반경 내 하수관로를 조회할 수 없습니다."
|
|
: "반경 내 하수관로 정보가 없습니다.",
|
|
]);
|
|
return [
|
|
{
|
|
name: "지반침하정보",
|
|
rows: [["항목", "내용"], ...options.summaryEntries],
|
|
columnWidths: [22, 75],
|
|
},
|
|
{
|
|
name: "하수관로목록",
|
|
rows: [
|
|
["사고번호", String(options.detail.acdntNo)],
|
|
["조회 반경(m)", options.radiusMeters],
|
|
["조회 기준", "기존 반경 조회 기준, 최대 200건 (가까운 거리순)"],
|
|
[],
|
|
["NO", "관로 ID", "용도", "경과년도(년)", "거리(m)"],
|
|
...pipeRows,
|
|
],
|
|
columnWidths: [12, 36, 24, 20, 18],
|
|
autoFilterRange: `A5:E${5 + pipes.length}`,
|
|
mergeRanges: ["B3:E3", ...(pipes.length === 0 ? ["A6:E6"] : [])],
|
|
},
|
|
];
|
|
};
|
|
|
|
/** 기존 반경 API를 다시 조회하여 실패를 빈 목록으로 내보내지 않는다. */
|
|
export const downloadSinkholeDetailExcel = async (options: DownloadOptions) => {
|
|
// 비동기 재조회 동안 호출자의 상세·반경·표시 행이 변경되어도 클릭 시점을 유지한다.
|
|
const snapshot: DownloadOptions = {
|
|
...options,
|
|
detail: { ...options.detail },
|
|
summaryEntries: options.summaryEntries.map(([title, value]) => [
|
|
title,
|
|
value,
|
|
]),
|
|
};
|
|
const { detail, radiusMeters } = snapshot;
|
|
const noCoordinates = String(detail.coordStatus ?? "").trim() !== "SUCCESS";
|
|
let pipes: SinkholeNearbyPipe[] = [];
|
|
if (!noCoordinates) {
|
|
const hasPoint =
|
|
detail.x != null &&
|
|
detail.y != null &&
|
|
String(detail.x).trim() !== "" &&
|
|
String(detail.y).trim() !== "" &&
|
|
Number.isFinite(Number(detail.x)) &&
|
|
Number.isFinite(Number(detail.y));
|
|
const params = new URLSearchParams({ radiusMeters: String(radiusMeters) });
|
|
if (hasPoint) {
|
|
params.set("x", String(detail.x));
|
|
params.set("y", String(detail.y));
|
|
}
|
|
const path = hasPoint
|
|
? "nearbyPipesByPoint"
|
|
: `${encodeURIComponent(detail.acdntNo)}/nearbyPipes`;
|
|
const raw = await getRawJson(
|
|
`/sewerage-service/api/v1/sinkholes/${path}?${params}`,
|
|
);
|
|
const response = raw?.data ?? raw;
|
|
if (response?.coordStatus !== "SUCCESS")
|
|
throw new Error(
|
|
"관로 조회 좌표를 확인할 수 없습니다. 상세정보를 다시 조회해 주세요.",
|
|
);
|
|
if (
|
|
!Array.isArray(response?.items) ||
|
|
response.items.some(
|
|
(item: unknown) =>
|
|
!item ||
|
|
typeof item !== "object" ||
|
|
Array.isArray(item) ||
|
|
!("pipeId" in item) ||
|
|
!("fid" in item),
|
|
)
|
|
)
|
|
throw new Error(
|
|
"관로 조회 응답이 올바르지 않습니다. 다시 시도해 주세요.",
|
|
);
|
|
pipes = response.items;
|
|
}
|
|
const now = new Date();
|
|
const date = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
await downloadExcelWorkbook({
|
|
fileName: `지반침하정보_${detail.acdntNo}_${date}.xlsx`,
|
|
sheets: buildSinkholeDetailSheets(snapshot, pipes, noCoordinates),
|
|
});
|
|
};
|