조사결과 관로·맨홀 지도 레이어와 보고서 조회 연동
This commit is contained in:
@@ -9,6 +9,7 @@ import type {
|
||||
ExmnImportValidationResult,
|
||||
ExmnJobType,
|
||||
ExmnPage,
|
||||
ExmnPipeReportAvailability,
|
||||
ExmnSectionSummaryRow,
|
||||
ExmnTemplateResult,
|
||||
PplFacility,
|
||||
@@ -285,7 +286,25 @@ export const downloadExmnReportTemplate = async (): Promise<Blob> =>
|
||||
export const downloadExmnBusinessReport = async (attachmentId: string): Promise<Blob> =>
|
||||
getBlobData(`/attach-service/api/v1/download/${encodeURIComponent(attachmentId)}`);
|
||||
|
||||
/** 저장된 페이지 범위를 이용해 선택 관로의 조사보고서 구간만 PDF로 조회한다. */
|
||||
export const downloadExmnPipeReport = async (
|
||||
exmnBizId: string,
|
||||
pipeNumber: string,
|
||||
): Promise<Blob> =>
|
||||
getBlobData(
|
||||
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/pipeReports/${encodeURIComponent(pipeNumber)}`,
|
||||
);
|
||||
|
||||
/** 사업 파일별 첨부서비스 키 쌍을 조사사업에 연결한다. */
|
||||
/** 팝업 생성 전에 선택 관로번호가 등록된 관로 PDF에 포함되는지 확인한다. */
|
||||
export const getExmnPipeReportAvailability = async (
|
||||
exmnBizId: string,
|
||||
pipeNumber: string,
|
||||
): Promise<ExmnPipeReportAvailability> =>
|
||||
getData(
|
||||
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/pipeReports/${encodeURIComponent(pipeNumber)}/availability`,
|
||||
) as Promise<ExmnPipeReportAvailability>;
|
||||
|
||||
export const updateExmnAttachments = async (
|
||||
exmnBizId: string,
|
||||
attachments: Partial<Record<ExmnFileType, ExmnAttachmentKey>>,
|
||||
|
||||
@@ -115,6 +115,12 @@ export interface ExmnImportCommitResult {
|
||||
summaryCount: number;
|
||||
}
|
||||
|
||||
/** 선택 관로의 조사보고서 팝업을 열 수 있는지 확인한 결과다. */
|
||||
export interface ExmnPipeReportAvailability {
|
||||
available: boolean;
|
||||
message?: string | null;
|
||||
}
|
||||
|
||||
/** 처리분구별 PPL 조사 집계 행이다. */
|
||||
export interface PplSummaryRow {
|
||||
trtdstId: string;
|
||||
|
||||
@@ -287,7 +287,7 @@ export function ExmnResultRegisterPopupPage({
|
||||
...current,
|
||||
[fileType]: [validationMessage],
|
||||
}));
|
||||
setNotice({ type: "error", text: validationMessage });
|
||||
setNotice(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -307,16 +307,7 @@ export function ExmnResultRegisterPopupPage({
|
||||
...current,
|
||||
[fileType]: result.errors.map((item) => item.message),
|
||||
}));
|
||||
const visibleErrors = result.errors.slice(0, 5).map((item) => item.message);
|
||||
if (result.errors.length > visibleErrors.length) {
|
||||
visibleErrors.push(`외 ${result.errors.length - visibleErrors.length}건`);
|
||||
}
|
||||
setNotice(result.valid
|
||||
? null
|
||||
: {
|
||||
type: "error",
|
||||
text: `검토 오류 ${result.errors.length}건\n${visibleErrors.join("\n")}`,
|
||||
});
|
||||
setNotice(null);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "조사파일 검토 중 오류가 발생했습니다.";
|
||||
setValidationResults((current) => {
|
||||
@@ -329,10 +320,7 @@ export function ExmnResultRegisterPopupPage({
|
||||
...current,
|
||||
[fileType]: [message],
|
||||
}));
|
||||
setNotice({
|
||||
type: "error",
|
||||
text: message,
|
||||
});
|
||||
setNotice(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -510,9 +498,8 @@ export function ExmnResultRegisterPopupPage({
|
||||
|
||||
{visibleValidationTypes.length > 0 && (
|
||||
<section className="overflow-hidden border border-border" aria-label="데이터 검토 내역">
|
||||
<div className="flex items-center justify-between bg-[#eaf3fb] px-3 py-2.5">
|
||||
<div className="flex items-center bg-[#eaf3fb] px-3 py-2.5">
|
||||
<h2 className="text-[1.3rem] font-medium text-label">데이터 검토 내역</h2>
|
||||
<span className="text-base text-label">파일별로 실제 확인한 항목입니다.</span>
|
||||
</div>
|
||||
{visibleValidationTypes.map((fileType) => {
|
||||
const status = fileValidationStatuses[fileType]!;
|
||||
|
||||
@@ -241,8 +241,21 @@ export function ExmnStatusDetailPanel({ business }: ExmnStatusDetailPanelProps)
|
||||
));
|
||||
};
|
||||
|
||||
const showMapPendingNotice = () => {
|
||||
setNotice({ type: "info", text: "지도보기는 현재 보류 기능이며 버튼 진입점만 표시합니다." });
|
||||
/** 선택 사업의 행정구역·사업차수·현재 조사유형을 하수관망도 초기 조회 계약으로 전달한다. */
|
||||
const openExmnMap = () => {
|
||||
if (!business) return;
|
||||
const query = new URLSearchParams({
|
||||
exmnBizId: business.exmnBizId,
|
||||
ctpvNm: business.ctpvNm,
|
||||
sggNm: business.sggNm,
|
||||
exmnJobType: jobType,
|
||||
});
|
||||
if (business.bizCycl != null) query.set("bizCycl", String(business.bizCycl));
|
||||
if (selectedSummaryRow) {
|
||||
query.set("trtdstId", selectedSummaryRow.trtdstId);
|
||||
query.set("trtdstNm", selectedSummaryRow.trtdstNm);
|
||||
}
|
||||
window.location.assign(`/map?${query.toString()}`);
|
||||
};
|
||||
|
||||
/** 현재 조사유형의 종합집계와 처리분구별 전체 상세 시트를 서버 XLSX로 내려받는다. */
|
||||
@@ -359,7 +372,13 @@ export function ExmnStatusDetailPanel({ business }: ExmnStatusDetailPanelProps)
|
||||
>
|
||||
상세보기
|
||||
</Button>
|
||||
<Button className={ACTION_BUTTON_CLASS} size="sm" onClick={showMapPendingNotice}>지도보기</Button>
|
||||
<Button
|
||||
className={ACTION_BUTTON_CLASS}
|
||||
size="sm"
|
||||
onClick={openExmnMap}
|
||||
>
|
||||
지도보기
|
||||
</Button>
|
||||
<Button
|
||||
color="green"
|
||||
className={DOWNLOAD_BUTTON_CLASS}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const windowPopupDefinitions = {
|
||||
key: "exmnResultRegister",
|
||||
title: "조사 결과 업로드",
|
||||
width: 1000,
|
||||
height: 500,
|
||||
height: 650,
|
||||
requiredParams: ["exmnBizId"],
|
||||
},
|
||||
exmnSurveyDetail: {
|
||||
@@ -21,6 +21,13 @@ export const windowPopupDefinitions = {
|
||||
height: 720,
|
||||
requiredParams: ["exmnBizId", "trtdstId", "trtdstNm", "jobType"],
|
||||
},
|
||||
exmnPipeReport: {
|
||||
key: "exmnPipeReport",
|
||||
title: "관로 조사보고서",
|
||||
width: 1200,
|
||||
height: 850,
|
||||
requiredParams: ["exmnBizId", "pipeNumber"],
|
||||
},
|
||||
oldPpUpload: {
|
||||
key: "oldPpUpload",
|
||||
title: "노후 하수관로 정비사업 등록",
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { WindowPopupRegistryItem } from "./model/types";
|
||||
import AgedPipePopupContent from "./ui/AgedPipePopupContent";
|
||||
import AgedPipeStatusByCtpvPopupContent from "./ui/AgedPipeStatusByCtpvPopupContent";
|
||||
import ExmnBusinessRegisterPopupContent from "./ui/ExmnBusinessRegisterPopupContent";
|
||||
import ExmnPipeReportPopupContent from "./ui/ExmnPipeReportPopupContent";
|
||||
import ExmnResultRegisterPopupContent from "./ui/ExmnResultRegisterPopupContent";
|
||||
import ExmnSurveyDetailPopupContent from "./ui/ExmnSurveyDetailPopupContent";
|
||||
import FloodPreTablePopupContent from "./ui/FloodPreTablePopupContent";
|
||||
@@ -50,6 +51,10 @@ export const windowPopupRegistry = {
|
||||
...windowPopupDefinitions.exmnSurveyDetail,
|
||||
component: ExmnSurveyDetailPopupContent,
|
||||
},
|
||||
exmnPipeReport: {
|
||||
...windowPopupDefinitions.exmnPipeReport,
|
||||
component: ExmnPipeReportPopupContent,
|
||||
},
|
||||
oldPpUpload: {
|
||||
...windowPopupDefinitions.oldPpUpload,
|
||||
component: OldPpUploadPopupContent,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { downloadExmnPipeReport } from "@/features/exmn/api/exmn-business-api";
|
||||
|
||||
import type { WindowPopupContentProps } from "../model/types";
|
||||
|
||||
const getParam = (value: string | string[] | undefined) =>
|
||||
Array.isArray(value) ? value[0] ?? "" : value ?? "";
|
||||
|
||||
/** 공통 Blob API 오류 문자열에서 백엔드의 사용자 안내 메시지만 추출한다. */
|
||||
const getReportErrorMessage = (error: unknown) => {
|
||||
if (!(error instanceof Error)) {
|
||||
return "관로 조사보고서를 불러오지 못했습니다.";
|
||||
}
|
||||
|
||||
const backendMessage = error.message.match(/"message":"((?:\\.|[^"\\])*)"/)?.[1];
|
||||
if (!backendMessage) return error.message;
|
||||
|
||||
try {
|
||||
return JSON.parse(`"${backendMessage}"`) as string;
|
||||
} catch {
|
||||
return backendMessage;
|
||||
}
|
||||
};
|
||||
|
||||
/** 저장된 관로별 PDF 구간을 공통 WindowPopup 안에서 표시한다. */
|
||||
export default function ExmnPipeReportPopupContent({
|
||||
searchParams,
|
||||
}: WindowPopupContentProps) {
|
||||
const exmnBizId = getParam(searchParams.exmnBizId);
|
||||
const pipeNumber = getParam(searchParams.pipeNumber);
|
||||
const reportNo = getParam(searchParams.reportNo);
|
||||
const [reportUrl, setReportUrl] = useState("");
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
let objectUrl = "";
|
||||
|
||||
setReportUrl("");
|
||||
setErrorMessage("");
|
||||
|
||||
void downloadExmnPipeReport(exmnBizId, pipeNumber)
|
||||
.then((blob) => {
|
||||
if (!active) return;
|
||||
|
||||
const pdfBlob =
|
||||
blob.type === "application/pdf"
|
||||
? blob
|
||||
: new Blob([blob], { type: "application/pdf" });
|
||||
objectUrl = URL.createObjectURL(pdfBlob);
|
||||
setReportUrl(objectUrl);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (!active) return;
|
||||
console.warn("관로 조사보고서 조회 실패:", error);
|
||||
setErrorMessage(getReportErrorMessage(error));
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
||||
};
|
||||
}, [exmnBizId, pipeNumber]);
|
||||
|
||||
return (
|
||||
<main className="sn-popup-page">
|
||||
<header className="sn-popup-header">
|
||||
<h1 className="sn-popup-title">관로 조사보고서</h1>
|
||||
<span className="text-[1.05rem] text-white">
|
||||
관로번호 {pipeNumber}
|
||||
{reportNo ? ` · 보고서번호 ${reportNo}` : ""}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<section className="min-h-0 flex-1 bg-container p-3">
|
||||
{errorMessage ? (
|
||||
<div className="flex h-full items-center justify-center rounded-sm border border-red-200 bg-red-50 p-6 text-center text-[1.1rem] text-red-700">
|
||||
{errorMessage}
|
||||
</div>
|
||||
) : reportUrl ? (
|
||||
<iframe
|
||||
title={`${pipeNumber} 관로 조사보고서`}
|
||||
src={reportUrl}
|
||||
className="h-full w-full border border-border bg-white"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center rounded-sm border border-border bg-white text-[1.1rem] text-muted-foreground">
|
||||
조사보고서를 불러오는 중입니다.
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user