조사사업 등록·결과 업로드 및 관로·맨홀 종합집계 화면 구현

This commit is contained in:
kiminchan
2026-07-23 17:06:01 +09:00
parent ec60a05b01
commit 477426c384
27 changed files with 3376 additions and 11 deletions
@@ -0,0 +1,183 @@
import {
getBlobData,
getData,
postMutiPart,
postRawJson,
putData,
} from "@/components/lib/api";
import type {
ExmnAttachmentKey,
ExmnBusiness,
ExmnBusinessDetail,
ExmnBusinessSaveRequest,
ExmnBusinessSearchParams,
ExmnFileType,
ExmnImportCommitResult,
ExmnImportValidationResult,
ExmnJobType,
ExmnPage,
ExmnSectionSummaryRow,
ExmnTemplateResult,
PplFacility,
PplFacilitySearchParams,
PplSummaryRow,
} from "../model/exmn-business-types";
/** 조사사업 API의 공통 기준 경로다. */
const EXMN_BUSINESS_API_PATH = "v1/exmnBusinesses";
/** 공통 응답 원문 또는 data 래퍼 양쪽에서 실제 payload를 해석한다. */
const unwrapData = <T>(response: T | { data?: T }): T =>
typeof response === "object" && response !== null && "data" in response
? (response as { data: T }).data
: (response as T);
/** Spring Page 응답이 비어 있거나 아직 미구현이어도 안전한 빈 Page로 정규화한다. */
const toPage = <T>(value: Partial<ExmnPage<T>> | null | undefined): ExmnPage<T> => ({
content: Array.isArray(value?.content) ? value.content : [],
totalElements: Number(value?.totalElements ?? 0),
number: Number(value?.number ?? 0),
size: Number(value?.size ?? 20),
totalPages: Number(value?.totalPages ?? 0),
});
/** 조사사업 목록을 조회조건·0-base 페이지·서버 정렬 계약으로 요청한다. */
export const getExmnBusinesses = async (
params: ExmnBusinessSearchParams,
): Promise<ExmnPage<ExmnBusiness>> => {
const query = new URLSearchParams({
page: String(params.page),
size: String(params.size),
});
if (params.bizCycl?.trim()) query.set("bizCycl", params.bizCycl.trim());
if (params.bizYr?.trim()) query.set("bizYr", params.bizYr.trim());
if (params.ctpvNm?.trim()) query.set("ctpvNm", params.ctpvNm.trim());
if (params.sggNm?.trim()) query.set("sggNm", params.sggNm.trim());
if (params.bizNm?.trim()) query.set("bizNm", params.bizNm.trim());
if (params.sort) query.set("sort", params.sort);
return toPage<ExmnBusiness>(await getData(`${EXMN_BUSINESS_API_PATH}?${query}`));
};
/** 선택 사업의 처리분구별 관로·맨홀 조사 상세현황을 조회한다. */
export const getExmnSectionSummaries = async (
exmnBizId: string,
jobType: ExmnJobType,
): Promise<ExmnSectionSummaryRow[]> => {
type SectionSummaryPayload = ExmnSectionSummaryRow[] | Partial<ExmnPage<ExmnSectionSummaryRow>>;
const response = await getData(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/sections?jobType=${jobType}`,
) as SectionSummaryPayload | { data?: SectionSummaryPayload };
const payload = unwrapData<SectionSummaryPayload>(response);
if (Array.isArray(payload)) return payload;
return Array.isArray(payload?.content) ? payload.content : [];
};
/** 선택한 조사사업의 처리분구·첨부를 포함한 상세정보를 조회한다. */
export const getExmnBusiness = async (exmnBizId: string): Promise<ExmnBusinessDetail> =>
getData(`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}`) as Promise<ExmnBusinessDetail>;
/** 조사사업과 선택 처리분구별 기본 요약행을 신규 저장한다. */
export const createExmnBusiness = async (
request: ExmnBusinessSaveRequest,
): Promise<ExmnBusinessDetail> =>
unwrapData<ExmnBusinessDetail>(await postRawJson(EXMN_BUSINESS_API_PATH, request));
/** 기존 조사사업 기본정보와 연결 처리분구를 같은 저장 계약으로 갱신한다. */
export const updateExmnBusiness = async (
exmnBizId: string,
request: ExmnBusinessSaveRequest,
): Promise<ExmnBusinessDetail> =>
putData(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}`,
request,
) as Promise<ExmnBusinessDetail>;
/** 저장된 사업의 처리분구별 PPL/MNHL 엑셀 시트명을 생성한다. */
export const createExmnTemplates = async (
exmnBizId: string,
trtdstIds: string[],
): Promise<ExmnTemplateResult> =>
unwrapData<ExmnTemplateResult>(await postRawJson(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/templates`,
{ trtdstIds },
));
/** 생성된 조사사업 입력 양식 파일을 내려받는다. */
export const downloadExmnTemplates = async (exmnBizId: string): Promise<Blob> =>
getBlobData(`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/templates/download`);
/** 사업 파일별 첨부서비스 키 쌍을 조사사업에 연결한다. */
export const updateExmnAttachments = async (
exmnBizId: string,
attachments: Partial<Record<ExmnFileType, ExmnAttachmentKey>>,
): Promise<Partial<Record<ExmnFileType, ExmnAttachmentKey>>> =>
putData(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/attachments`,
{ attachments },
) as Promise<Partial<Record<ExmnFileType, ExmnAttachmentKey>>>;
/** 원본 PPL/MNHL 파일의 업무·JSON 정합성을 서버에서 검토한다. */
export const validateExmnImport = async (
exmnBizId: string,
formData: FormData,
): Promise<ExmnImportValidationResult> =>
postMutiPart(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/imports/validate`,
formData,
) as Promise<ExmnImportValidationResult>;
/** 검토했던 동일 파일과 첨부키를 다시 보내 시설 저장·요약 집계를 확정한다. */
export const commitExmnImport = async (
exmnBizId: string,
formData: FormData,
): Promise<ExmnImportCommitResult> =>
postMutiPart(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/imports/commit`,
formData,
) as Promise<ExmnImportCommitResult>;
/** 선택 사업의 처리분구별 PPL 집계를 조회한다. */
export const getPplSummaries = async (
exmnBizId: string,
params: { trtdstId?: string; page: number; size: number; sort?: string },
): Promise<ExmnPage<PplSummaryRow>> => {
const query = new URLSearchParams({ page: String(params.page), size: String(params.size) });
if (params.trtdstId) query.set("trtdstId", params.trtdstId);
if (params.sort) query.set("sort", params.sort);
return toPage<PplSummaryRow>(await getData(
`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/summaries/ppl?${query}`,
));
};
/** 선택 사업의 PPL 시설을 처리분구·구간·GIS번호·등급으로 조회한다. */
export const getPplFacilities = async (
params: PplFacilitySearchParams,
): Promise<ExmnPage<PplFacility>> => {
const query = new URLSearchParams({
exmnBizId: params.exmnBizId,
jobType: "PPL",
page: String(params.page),
size: String(params.size),
});
if (params.trtdstId) query.set("trtdstId", params.trtdstId);
if (params.sectionType) query.set("sectionType", params.sectionType);
if (params.gisManagementNo?.trim()) query.set("gisManagementNo", params.gisManagementNo.trim());
if (params.structuralGrade) query.set("structuralGrade", params.structuralGrade);
if (params.operationalGrade) query.set("operationalGrade", params.operationalGrade);
if (params.defectType && params.defectType !== "ALL") query.set("defectType", params.defectType);
if (params.sort) query.set("sort", params.sort);
return toPage<PplFacility>(await getData(`v1/exmnFacilities?${query}`));
};
/** 현재 PPL 조회조건을 서버 엑셀로 내려받는다. */
export const downloadPplExport = async (
exmnBizId: string,
trtdstId?: string,
): Promise<Blob> => {
const query = new URLSearchParams();
if (trtdstId) query.set("trtdstId", trtdstId);
const queryString = query.toString();
const suffix = queryString ? `?${queryString}` : "";
return getBlobData(`${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/exports/ppl${suffix}`);
};