[st frontend] 저장시 picRvwCn 검토 내용을 추가 저장 로직

This commit is contained in:
2026-06-01 17:22:32 +09:00
parent 9b9edec627
commit b704db99b1
2 changed files with 63 additions and 25 deletions
@@ -88,6 +88,7 @@ export interface InstlSncnCklstResultSaveDto {
cklstRvwSn: number;
rvwStpComCd: string;
selected: "Y" | "N" | null;
picRvwCn?: string | null;
}
/** 설치인가 신청 - 필수첨부파일 저장 DTO(배열 원소) */
@@ -102,6 +103,7 @@ export interface InstlSncnApplySaveRequestDto {
bzScaleList: InstlSncnBzScaleSaveDto[];
bizCstList: InstlSncnBizCstSaveRequestDto[];
cklstResultList: InstlSncnCklstResultSaveDto[];
cklstResultMap?: Record<string, InstlSncnCklstResultSaveDto[]>;
esntlAtchFileList: InstlSncnEsntlAtchFileSaveDto[];
}
@@ -102,7 +102,7 @@ const INSTL_PROGRESS_ORDER = [
const getProgressStepCount = (prgrsSttsComCd?: string): number => {
const idx = INSTL_PROGRESS_ORDER.indexOf(prgrsSttsComCd ?? "ST");
if (idx <= INSTL_PROGRESS_ORDER.indexOf("ST")) return 1;
if (idx <= INSTL_PROGRESS_ORDER.indexOf("TP")) return 2;
if (idx <= INSTL_PROGRESS_ORDER.indexOf("ER")) return 2;
return 3;
};
@@ -152,7 +152,12 @@ const normalizeChecklistResultRows = (payload: any): any[] => {
*/
function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
const router = useRouter();
const userInfo = useUserStore((state) => state.userInfo);
const userInfo2 = useUserStore((state) => state.userInfo);
// userInfo?.dtlInstCd 테스트 코드 ENV.ROLE_USER03 , KECO 권한 테스트용 (실제 권한에 맞게 변경 필요)
const userInfo = {
...userInfo2,
dtlInstCd: "ROLE_USER03",
};
const alert = useAlertStore((state) => state.setMessage);
const qc = useQueryClient();
const currentDet = useMemo(() => initialData?.detDto ?? initialData, [initialData]);
@@ -177,14 +182,17 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
useEffect(() => {
if (visibleReviewStepCodes.length === 0) return;
if (editableReviewStep && selectedReviewStep !== editableReviewStep) {
setSelectedReviewStep(editableReviewStep);
return;
}
if (!visibleReviewStepCodes.includes(selectedReviewStep)) {
setSelectedReviewStep(visibleReviewStepCodes[visibleReviewStepCodes.length - 1]);
}
}, [editableReviewStep, selectedReviewStep, visibleReviewStepCodes]);
// Only set default/initial selection when visible steps or editable step change.
// Do NOT include `selectedReviewStep` in deps to avoid overwriting user clicks.
setSelectedReviewStep((current) => {
// keep current if still visible
if (current && visibleReviewStepCodes.includes(current)) return current;
// prefer editable step if available
if (editableReviewStep) return editableReviewStep;
// otherwise pick the last visible step
return visibleReviewStepCodes[visibleReviewStepCodes.length - 1];
});
}, [editableReviewStep, visibleReviewStepCodes]);
const docId = useMemo(() => {
return currentDet?.instlSncnDocNo ?? "";
@@ -594,6 +602,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
/* ── 체크리스트 ── */
type CheckResultVal = "Y" | "N" | null;
const [checkResults, setCheckResults] = useState<Record<string, CheckResultVal>>({});
const [checkComments, setCheckComments] = useState<Record<string, string>>({});
const cklstRowRefs = useRef<Record<string, HTMLTableRowElement | null>>({});
const { data: cklstTree = [] } = useQuery<CklstTreeNode[]>({
@@ -707,6 +716,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
if (!Array.isArray(results) || results.length === 0) return;
const lookup = new Map<string, string>();
const commentLookup = new Map<string, string>();
const stepCodesInResult = new Set<ReviewStepCode>();
for (const r of results) {
const stepCode = (r.rvwStpComCd ?? DEFAULT_RVW_STP_COM_CD) as ReviewStepCode;
@@ -714,6 +724,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
stepCodesInResult.add(stepCode);
const key = `${r.cklstSn}_${r.cklstArtclSn ?? ""}_${r.cklstRvwSn}_${stepCode}`;
if (r.rvwRsltAnmlyYn === "Y" || r.rvwRsltAnmlyYn === "N") lookup.set(key, r.rvwRsltAnmlyYn);
if (r.picRvwCn) commentLookup.set(key, String(r.picRvwCn));
}
setCheckResults((prev) => {
@@ -735,6 +746,18 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
}
return changed ? next : prev;
});
// set comment state separately to avoid rerendering conflicts
setCheckComments((prev) => {
const next: Record<string, string> = { ...prev };
let changed = false;
for (const [k, v] of commentLookup.entries()) {
if (next[k] !== v) {
next[k] = v;
changed = true;
}
}
return changed ? next : prev;
});
}, [initialData, cklstRows, visibleReviewStepCodes]);
const cklstStats = useMemo(() => {
@@ -759,24 +782,19 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
});
visibleReviewStepCodes.forEach((code) => persistedStepCodeSet.add(code));
return Array.from(persistedStepCodeSet).flatMap((stepCode) =>
return Array.from(persistedStepCodeSet).flatMap((rvwStpComCd) =>
cklstRows.map((row) => ({
cklstSn: row.cklstSn,
cklstArtclSn: row.cklstArtclSn,
cklstRvwSn: row.cklstRvwSn,
rvwStpComCd: stepCode,
selected: checkResults[cklstKey(row, stepCode)] ?? null,
rvwStpComCd: rvwStpComCd,
selected: checkResults[cklstKey(row, rvwStpComCd)] ?? null,
picRvwCn: checkComments[cklstKey(row, rvwStpComCd)] ?? null,
})),
);
};
// 맵 형식으로 저장할 경우(업서트 처리용) - 키는 상세 코드(EX: LOCAL.지자체) 포맷
const REVIEW_STEP_FULL: Record<ReviewStepCode, string> = {
LOCAL: "LOCAL.지자체",
ENV: "ENV.환경청",
KECO: "KECO.환경공단",
};
// 맵 형식으로 저장할 경우(업서트 처리용) - 키는 검토단계 코드(LOCAL/ENV/KECO) 사용
const toCklstSaveDtoMap = () => {
const persistedStepCodeSet = new Set<ReviewStepCode>();
normalizeChecklistResultRows(initialData).forEach((r: any) => {
@@ -786,14 +804,15 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
visibleReviewStepCodes.forEach((code) => persistedStepCodeSet.add(code));
const result: Record<string, any[]> = {};
Array.from(persistedStepCodeSet).forEach((stepCode) => {
const fullKey = REVIEW_STEP_FULL[stepCode];
result[fullKey] = cklstRows.map((row) => ({
Array.from(persistedStepCodeSet).forEach((rvwStpComCd) => {
// use code as key (e.g., "LOCAL") and set rvwStpComCd to the same code
result[rvwStpComCd] = cklstRows.map((row) => ({
cklstSn: row.cklstSn,
cklstArtclSn: row.cklstArtclSn,
cklstRvwSn: row.cklstRvwSn,
rvwStpComCd: fullKey,
selected: checkResults[cklstKey(row, stepCode)] ?? null,
rvwStpComCd: rvwStpComCd,
selected: checkResults[cklstKey(row, rvwStpComCd)] ?? null,
picRvwCn: checkComments[cklstKey(row, rvwStpComCd)] ?? null,
}));
});
@@ -1525,6 +1544,11 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
</th>
<th rowSpan={2}>주요 검토내용</th>
<th colSpan={2}>검토 결과(-)</th>
{selectedReviewStep !== "LOCAL" ? (
<th rowSpan={2} className="w-[20rem]">
기술검토 의견
</th>
) : null}
</tr>
<tr>
<th className="w-[9rem]">
@@ -1611,6 +1635,18 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
/>
</label>
</td>
{selectedReviewStep !== "LOCAL" ? (
<td className="text-left">
<textarea
className="cnfm-input w-full h-20"
value={checkComments[key] ?? ""}
readOnly={editableReviewStep !== selectedReviewStep}
onChange={(e) =>
setCheckComments((prev) => ({ ...prev, [key]: e.target.value }))
}
/>
</td>
) : null}
</tr>
);
})