[st frontend] as 수정에 폼은 등록폼을 같이 쓰도록 코딩함
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { CnfmRegistPage } from "./cnfm-regist-page";
|
||||
|
||||
import { InstlSncnApi } from "@/entities/instl-sncn";
|
||||
|
||||
import "./cnfm-page.css";
|
||||
|
||||
function CnfmModifyPage({ id }: { id: string }) {
|
||||
const router = useRouter();
|
||||
const query = useQuery({
|
||||
queryKey: ["instl-sncn-modify", id],
|
||||
// share cache with view page
|
||||
queryKey: ["instl-sncn-detail", id],
|
||||
queryFn: () => InstlSncnApi.getById(id),
|
||||
});
|
||||
|
||||
@@ -25,53 +24,11 @@ function CnfmModifyPage({ id }: { id: string }) {
|
||||
|
||||
const data = query.data;
|
||||
|
||||
if (!data) return <div className="mt-6 text-[1.4rem]">수정 대상을 찾을 수 없습니다.</div>;
|
||||
|
||||
return (
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="cnfm-card p-4">
|
||||
<h2 className="cnfm-section-title">설치인가 수정</h2>
|
||||
<p className="mt-2 text-[1.3rem] text-gray-600">
|
||||
상세 조회 API 데이터를 기반으로 수정 화면을 구성했습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="cnfm-card p-4">
|
||||
<h3 className="text-[1.5rem] font-semibold">기본 정보</h3>
|
||||
<table className="cnfm-form-table mt-3">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>문서번호</th>
|
||||
<td>{data.instlSncnDocNo}</td>
|
||||
<th>진행상태</th>
|
||||
<td>{data.prgrsSttsCdNm || data.prgrsSttsComCd || "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>사업명</th>
|
||||
<td>
|
||||
<Input defaultValue={data.bizNm || ""} className="!text-[1.3rem]" />
|
||||
</td>
|
||||
<th>사업내용</th>
|
||||
<td>
|
||||
<Input defaultValue={data.bizCn || ""} className="!text-[1.3rem]" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>설계자명</th>
|
||||
<td>
|
||||
<Input defaultValue={data.dsgnrNm || ""} className="!text-[1.3rem]" />
|
||||
</td>
|
||||
<th>설계자 연락처</th>
|
||||
<td>
|
||||
<Input defaultValue={data.dsgnrTelno || ""} className="!text-[1.3rem]" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="cnfm-action-row">
|
||||
<Button color="white" onClick={() => router.push(`/instl-sncn/${id}`)}>상세로</Button>
|
||||
<Button type="button" onClick={() => window.alert("수정 API 연동은 추후 확장 가능합니다.")}>저장</Button>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<CnfmRegistPage initialData={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -76,7 +77,7 @@ import { NumCell, ReadCell, PrivacyConsent, EtcAttachments } from '@/widgets/cnf
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 메인 컴포넌트 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
function CnfmRegistPage() {
|
||||
function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
|
||||
const router = useRouter();
|
||||
const userInfo = useUserStore((state) => state.userInfo);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
@@ -112,6 +113,12 @@ function CnfmRegistPage() {
|
||||
const [dsgnrTelno, setDsgnrTelno] = useState("");
|
||||
const [dsgnrEmlAddr, setDsgnrEmlAddr] = useState("");
|
||||
|
||||
/* ── 신청자 정보 (수정 모드 매핑용) ── */
|
||||
const [aplcntNm, setAplcntNm] = useState("");
|
||||
const [aplcntMblTelno, setAplcntMblTelno] = useState("");
|
||||
const [aplcntOgdpNm, setAplcntOgdpNm] = useState("");
|
||||
const [aplcntEmlAddr, setAplcntEmlAddr] = useState("");
|
||||
|
||||
/* ── 개인정보 동의 ── */
|
||||
const [privacyAgree, setPrivacyAgree] = useState<"Y" | "N">("N");
|
||||
|
||||
@@ -222,6 +229,122 @@ function CnfmRegistPage() {
|
||||
const [etcUploading, setEtcUploading] = useState(false);
|
||||
const etcFileGroupIdRef = useRef("");
|
||||
|
||||
// 초기 데이터가 주어지면 폼 상태로 매핑 (수정 모드에서 사용)
|
||||
useEffect(() => {
|
||||
if (!initialData) return;
|
||||
|
||||
try {
|
||||
const det = (initialData.detDto ?? initialData) as any;
|
||||
|
||||
const bizSclList = initialData.bizSclList ?? [];
|
||||
const bizCstList = initialData.bizCstList ?? [];
|
||||
const cklstResultList = initialData.cklstResultList ?? [];
|
||||
const esntlFileList = initialData.esntlFileList ?? [];
|
||||
const etcFileList = initialData.etcFileList ?? [];
|
||||
|
||||
|
||||
// 기본 정보 및 설계자 정보
|
||||
setBzNm(det.bizNm ?? "");
|
||||
setBzBgngYr(det.bizBgngYr ?? "");
|
||||
setBzEndYr(det.bizEndYr ?? "");
|
||||
setBzCn(det.bizCn ?? "");
|
||||
setInstlCnfmApr(det.envoSeComCd ?? det.envoSeCd ?? "CNFM");
|
||||
|
||||
// 설계자
|
||||
setDsgnrNm(det.dsgnrNm ?? "");
|
||||
setDsgnrPsitn(det.dsgnrOgdpNm ?? "");
|
||||
setDsgnrTelno(det.dsgnrTelno ?? "");
|
||||
setDsgnrEmlAddr(det.dsgnrEmlAddr ?? "");
|
||||
|
||||
// 개인정보 동의
|
||||
if (det.privacyAgree) setPrivacyAgree(det.privacyAgree === "Y" ? "Y" : "N");
|
||||
|
||||
// 신청자
|
||||
setAplcntNm(det.aplcntNm ?? "");
|
||||
setAplcntMblTelno(det.aplcntMblTelno ?? "");
|
||||
setAplcntOgdpNm(det.aplcntOgdpNm ?? "");
|
||||
setAplcntEmlAddr(det.aplcntEmlAddr ?? "");
|
||||
|
||||
// 기타첨부파일
|
||||
if (Array.isArray(etcFileList)) {
|
||||
setEtcFiles(
|
||||
etcFileList
|
||||
.filter((f: any) => f && typeof f === "object")
|
||||
.map((f: any) => ({
|
||||
id: f.atflId ?? String(Math.random()),
|
||||
name: f.orgnlFileNm ?? f.physFileNm ?? "",
|
||||
ext: "",
|
||||
size: f.atflFlieSz ?? 0,
|
||||
uploader: f.upldUserNm ?? "",
|
||||
atflCd: f.atflCd ?? "",
|
||||
atflId: f.atflId ?? "",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
// 필수첨부파일: requiredFiles는 requiredAttachmentDefs 로 초기화되므로 atflCd/atflId만 덮어쓴다
|
||||
if (Array.isArray(esntlFileList)) {
|
||||
setRequiredFiles((prev) => {
|
||||
const next = { ...prev };
|
||||
for (const f of esntlFileList) {
|
||||
if (!f || typeof f !== "object") continue;
|
||||
const key = f.esntlAtchFileId ?? f.esntlAtchFileId;
|
||||
if (!key) continue;
|
||||
const existing = next[key] ?? {
|
||||
key,
|
||||
esntlAtchFileNm: f.esntlAtchFileNm ?? "",
|
||||
esntlAtchYn: f.esntlAtchYn ?? "N",
|
||||
esntlAtchFileRmrk: "",
|
||||
atflCd: "",
|
||||
atflId: "",
|
||||
fileName: "",
|
||||
uploading: false,
|
||||
};
|
||||
|
||||
next[key] = {
|
||||
...existing,
|
||||
atflCd: f.atflCd ?? existing.atflCd,
|
||||
atflId: f.atflId ?? existing.atflId,
|
||||
fileName: f.orgnlFileNm ?? existing.fileName,
|
||||
uploading: false,
|
||||
};
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
// 사업규모 매핑
|
||||
if (Array.isArray(bizSclList)) {
|
||||
const map: Record<string, BzScaleRow> = {};
|
||||
for (const row of bizSclList) {
|
||||
if (!row.cnstpSeCd) continue;
|
||||
map[row.cnstpSeCd] = {
|
||||
bscPl: String(row.bscPl ?? row.bscPlanQty ?? ""),
|
||||
budgetReflct: String(row.budgetReflct ?? row.bgtRfltQty ?? ""),
|
||||
thtiDsgn: String(row.thtiDsgn ?? row.tstmDesignQty ?? ""),
|
||||
};
|
||||
}
|
||||
setBzScaleMap(map);
|
||||
}
|
||||
|
||||
// 총사업비 매핑
|
||||
if (Array.isArray(bizCstList)) {
|
||||
const map: Record<string, BzAmtRow> = {};
|
||||
for (const row of bizCstList) {
|
||||
if (!row.acntgSeCd) continue;
|
||||
map[row.acntgSeCd] = {
|
||||
bscPl: String(row.bscPlanAmt ?? row.bscPl ?? ""),
|
||||
budgetReflct: String(row.bgtRfltAmt ?? row.budgetReflct ?? ""),
|
||||
thtiDsgn: String(row.tstmDesignAmt ?? row.thtiDsgn ?? ""),
|
||||
};
|
||||
}
|
||||
setAmtMap(map);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("initialData 매핑 실패", err);
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
/** 기타첨부파일 삭제 핸들러: 백엔드에 DELETE 요청을 보내 소프트삭제 처리 후 상태 갱신 */
|
||||
const handleDeleteEtcFile = async (fileId: string, atflId: string) => {
|
||||
try {
|
||||
@@ -425,6 +548,34 @@ function CnfmRegistPage() {
|
||||
});
|
||||
}, [cklstRows]);
|
||||
|
||||
// 수정 모드에서 서버의 체크리스트 결과값을 폼 상태로 반영
|
||||
useEffect(() => {
|
||||
if (!initialData || cklstRows.length === 0) return;
|
||||
const det = (initialData.detDto ?? initialData) as any;
|
||||
if (!Array.isArray(det.cklstResultList)) return;
|
||||
|
||||
const lookup = new Map<string, string>();
|
||||
for (const r of det.cklstResultList) {
|
||||
// 서버의 결과에 cklstSn, cklstArtclSn, cklstRvwSn 조합이 있다면 key 생성
|
||||
const key = `${r.cklstSn}_${r.cklstArtclSn ?? ""}_${r.cklstRvwSn}`;
|
||||
if (r.rvwRsltAnmlyYn === "Y" || r.rvwRsltAnmlyYn === "N") lookup.set(key, r.rvwRsltAnmlyYn);
|
||||
}
|
||||
|
||||
setCheckResults((prev) => {
|
||||
const next = { ...prev };
|
||||
let changed = false;
|
||||
for (const row of cklstRows) {
|
||||
const k = cklstKey(row);
|
||||
const v = lookup.get(k);
|
||||
if (v && next[k] !== v) {
|
||||
next[k] = v as CheckResultVal;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed ? next : prev;
|
||||
});
|
||||
}, [initialData, cklstRows]);
|
||||
|
||||
const cklstStats = useMemo(() => {
|
||||
const total = cklstRows.length;
|
||||
const completed = cklstRows.reduce((acc, row) => {
|
||||
@@ -663,10 +814,10 @@ function CnfmRegistPage() {
|
||||
bizBgngYr: bzBgngYr,
|
||||
bizEndYr: bzEndYr,
|
||||
bizCn: bzCn,
|
||||
aplcntNm: "",
|
||||
aplcntMblTelno: "",
|
||||
aplcntOgdpNm: "",
|
||||
aplcntEmlAddr: "",
|
||||
aplcntNm: aplcntNm || userInfo?.userName || "",
|
||||
aplcntMblTelno: aplcntMblTelno || (userInfo?.mphn ?? userInfo?.telno ?? ""),
|
||||
aplcntOgdpNm: aplcntOgdpNm || (userInfo?.instSeNm ?? ""),
|
||||
aplcntEmlAddr: aplcntEmlAddr || (userInfo?.email ?? ""),
|
||||
dsgnrNm,
|
||||
dsgnrTelno,
|
||||
dsgnrOgdpNm: dsgnrPsitn,
|
||||
@@ -852,22 +1003,22 @@ function CnfmRegistPage() {
|
||||
<th rowSpan={2} style={{ textAlign: "center" }} className="text-center align-middle w-[6.5rem]">신청자</th>
|
||||
<th style={{ textAlign: "center" }}>이름</th>
|
||||
<td>
|
||||
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">{userInfo?.userName ?? "\u00A0"}</span>
|
||||
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">{aplcntNm || userInfo?.userName || "\u00A0"}</span>
|
||||
</td>
|
||||
<th style={{ textAlign: "center" }}>전화번호</th>
|
||||
<td>
|
||||
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">{userInfo?.mphn ?? userInfo?.telno ?? "\u00A0"}</span>
|
||||
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">{aplcntMblTelno || userInfo?.mphn || userInfo?.telno || "\u00A0"}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style={{ textAlign: "center" }}>부서</th>
|
||||
<td>
|
||||
<span className="text-[1.3rem] text-gray-500">{userInfo?.instSeNm ?? "\u00A0"}</span>
|
||||
<span className="text-[1.3rem] text-gray-500">{aplcntOgdpNm || userInfo?.instSeNm || "\u00A0"}</span>
|
||||
</td>
|
||||
|
||||
<th style={{ textAlign: "center" }}>이메일</th>
|
||||
<td>
|
||||
<span className="text-[1.3rem] text-gray-500">{userInfo?.email ?? "\u00A0"}</span>
|
||||
<span className="text-[1.3rem] text-gray-500">{aplcntEmlAddr || userInfo?.email || "\u00A0"}</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user