[as instl service] 신청 저장 로직 추가

This commit is contained in:
2026-05-21 18:25:50 +09:00
parent ad13875ce8
commit d3434060ba
4 changed files with 138 additions and 15 deletions
@@ -1,6 +1,7 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
InstlSncnApplySaveRequestDto,
InstlSncnMng,
InstlSncnMngSearchParams,
InstlSncnMngService,
@@ -19,6 +20,14 @@ export class InstlSncnApiService implements InstlSncnMngService {
const response = await client.get<InstlSncnMng>(`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/${id}`);
return response.data;
}
public async saveApply(payload: InstlSncnApplySaveRequestDto): Promise<string> {
const response = await client.post<string>(
`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/save-apply`,
payload,
);
return response.data;
}
}
export const InstlSncnApi = new InstlSncnApiService();
+3
View File
@@ -1,7 +1,10 @@
export { InstlSncnApi } from "./api/instl-sncn-service";
export {
INSTL_SNCN_REGISTER_SELECTION_KEY,
type InstlSncnApplySaveRequestDto,
type InstlSncnBizCstSaveRequestDto,
type InstlSncnBzScaleSaveDto,
type InstlSncnCklstResultSaveDto,
type InstlSncnMng,
type InstlSncnMngSearchParams,
type SpringPage,
@@ -80,7 +80,24 @@ export interface InstlSncnBizCstSaveRequestDto {
tstmDesignAmt: number;
}
/** 설치인가 신청 - 체크리스트 결과 저장 DTO(배열 원소) */
export interface InstlSncnCklstResultSaveDto {
cklstSn: number;
cklstArtclSn: number;
cklstRvwSn: number;
selected: "Y" | "N" | null;
}
/** 설치인가 신청 - 통합 저장 요청 DTO */
export interface InstlSncnApplySaveRequestDto {
instlSncnMng: InstlSncnMng;
bzScaleList: InstlSncnBzScaleSaveDto[];
bizCstList: InstlSncnBizCstSaveRequestDto[];
cklstResultList: InstlSncnCklstResultSaveDto[];
}
export interface InstlSncnMngService {
getPage: (params: InstlSncnMngSearchParams) => Promise<SpringPage<InstlSncnMng>>;
getById: (id: string) => Promise<InstlSncnMng>;
saveApply: (payload: InstlSncnApplySaveRequestDto) => Promise<string>;
}
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
@@ -13,7 +13,11 @@ import type { CnstpSeMngTreeDto } from "@/entities/cnstp";
import { cnstpService } from "@/entities/cnstp";
import {
INSTL_SNCN_REGISTER_SELECTION_KEY,
InstlSncnApi,
type InstlSncnApplySaveRequestDto,
type InstlSncnBizCstSaveRequestDto,
type InstlSncnBzScaleSaveDto,
type InstlSncnMng,
} from "@/entities/instl-sncn";
import { Button, Input } from "@common/components";
@@ -103,8 +107,9 @@ function CnfmRegistPage() {
const [etcNoticeOpen, setEtcNoticeOpen] = useState(true);
/* ── 체크리스트 ── */
type CheckResultVal = "Y" | "N" | "";
type CheckResultVal = "Y" | "N" | null;
const [checkResults, setCheckResults] = useState<Record<string, CheckResultVal>>({});
const cklstRowRefs = useRef<Record<string, HTMLTableRowElement | null>>({});
const { data: cklstTree = [] } = useQuery<CklstTreeNode[]>({
queryKey: ["cklstTree", "INSTL", taskSeComCd],
@@ -159,7 +164,7 @@ function CnfmRegistPage() {
const next: Record<string, CheckResultVal> = {};
cklstRows.forEach((row) => {
const key = cklstKey(row);
next[key] = prev[key] ?? "";
next[key] = prev[key] ?? null;
});
return next;
});
@@ -181,9 +186,8 @@ function CnfmRegistPage() {
cklstSn: row.cklstSn,
cklstArtclSn: row.cklstArtclSn,
cklstRvwSn: row.cklstRvwSn,
selected: checkResults[cklstKey(row)] ?? "",
}))
.filter((row) => row.selected !== "");
selected: checkResults[cklstKey(row)] ?? null,
}));
/* ── 사업규모 (공종구분 트리 연동) ── */
// cnstpSeCd → { bscPl, budgetReflct, thtiDsgn } 단일 map
@@ -277,6 +281,20 @@ function CnfmRegistPage() {
[acntgSeCd]: { ...(prev[acntgSeCd] ?? emptyAmt()), [col]: val },
}));
/** 총사업비 맵 -> 백엔드 저장용 배열 DTO 변환 */
const toBizCstSaveDtoList = (): InstlSncnBizCstSaveRequestDto[] =>
acntgTree
.filter((node) => leafSet.has(node.acntgSeCd))
.map((node) => {
const row = amtMap[node.acntgSeCd] ?? emptyAmt();
return {
acntgSeCd: node.acntgSeCd,
bscPlanAmt: num(row.bscPl),
bgtRfltAmt: num(row.budgetReflct),
tstmDesignAmt: num(row.thtiDsgn),
};
});
/**
* indAcntgSeCdNm 의 앞 공백 수를 파싱해 padding px 값으로 반환
* (공백 4칸 = 1 depth = 1rem)
@@ -306,6 +324,7 @@ function CnfmRegistPage() {
/* ── 검증 ── */
function fnValidation(mode: "SAVE" | "PRESENTN") {
/*
if (!bzNm.trim()) {
alert("사업명을 입력하세요.");
return false;
@@ -322,26 +341,96 @@ function CnfmRegistPage() {
alert("개인정보 제공에 동의하셔야 제출이 가능합니다.");
return false;
}
// 체크리스트 사용 항목(Y) 중 미선택 항목이 있으면 진행 불가
const hasUncheckedChecklist = cklstRows.some(
(row) => !checkResults[cklstKey(row)],
);
if (hasUncheckedChecklist) {
alert("체크리스트를 모두 체크하세요.");
return false;
}
// 저장 시: 사용 항목(Y)은 검토 결과가 반드시 예(Y)여야 함
*/
if (mode === "SAVE") {
const invalidYesRow = cklstRows.find(
(row) => row.bchckUseYn === "Y" && checkResults[cklstKey(row)] !== "Y",
);
if (invalidYesRow) {
const invalidKey = cklstKey(invalidYesRow);
const invalidRowEl = cklstRowRefs.current[invalidKey];
invalidRowEl?.scrollIntoView({ behavior: "smooth", block: "center" });
const yesRadio = invalidRowEl?.querySelector<HTMLInputElement>(
`input[name="chk_${invalidKey}"][value="Y"]`,
);
yesRadio?.focus();
alert(`${invalidYesRow.rvwCn} 검토내용은 예(Y)여야 합니다.`);
return false;
}
}
return true;
}
function handleSave() {
const toInstlSncnMngPayload = (prgrsSttsComCd: "SV" | "ST"): InstlSncnMng => ({
instlSncnDocNo: "",
dongId: "",
sidoNm: "",
sggNm: "",
chrgrNm: "",
instNm: "",
envoSeComCd: instlCnfmApr,
envoSeCdNm: "",
taskSeComCd,
taskSeCdNm: taskSeComCdNm,
bgtBizCd: "",
bgtAplyYr: "",
bizNm: bzNm,
bizBgngYr: bzBgngYr,
bizEndYr: bzEndYr,
bizCn: bzCn,
aplcntNm: "",
aplcntMblTelno: "",
aplcntOgdpNm: "",
aplcntEmlAddr: "",
dsgnrNm,
dsgnrTelno,
dsgnrOgdpNm: dsgnrPsitn,
dsgnrEmlAddr,
atchFileGroupId: "",
prgrsSttsComCd,
prgrsSttsCdNm: "",
regDt: "",
mdfcnDt: "",
rgtrNm: "",
});
async function handleSave() {
if (!fnValidation("SAVE")) return;
const payload = {
taskSeComCd,
const payload: InstlSncnApplySaveRequestDto = {
instlSncnMng: toInstlSncnMngPayload("SV"), // 저장 시 진행상태는 SV (임시저장)
bzScaleList: toBzScaleSaveDtoList(),
bizCstList: toBizCstSaveDtoList(),
cklstResultList: toCklstSaveDtoList(),
};
void payload;
alert("저장(임시) API 연동 후 progrsSttus=SV 전송",JSON.stringify(payload, null, 2));
};
try {
const issuedDocNo = await InstlSncnApi.saveApply(payload);
alert(`저장되었습니다. 문서번호: ${issuedDocNo}`);
} catch (error) {
console.error("저장 API 호출 실패", error);
alert("저장 중 오류가 발생했습니다.");
}
}
function handleSubmit() {
if (!fnValidation("PRESENTN")) return;
const payload = {
taskSeComCd,
instlSncnMng: toInstlSncnMngPayload("ST"),
bzScaleList: toBzScaleSaveDtoList(),
cklstResultList: toCklstSaveDtoList(),
bizCstList: toBizCstSaveDtoList(),
cklstRvwRsltList: toCklstSaveDtoList(),
};
void payload;
alert("제출 API 연동 후 progrsSttus=ST 전송");
@@ -812,7 +901,12 @@ function CnfmRegistPage() {
const rvwPaddingLeft = `${Math.max(0, row.treeLevel - 1) * 16 + 8}px`;
return (
<tr key={key}>
<tr
key={key}
ref={(el) => {
cklstRowRefs.current[key] = el;
}}
>
<td className="text-center text-[1.2rem]">{row.cklstArtclNm}</td>
<td className="!text-left pl-2 text-[1.2rem] text-gray-600" style={{ paddingLeft: rvwPaddingLeft }}>
{row.treeLevel > 1 ? "└ " : ""}