Merge branch 'iipbs/as/dev'
Deploy Frontend / deploy (push) Successful in 1m1s

This commit is contained in:
2026-06-16 15:05:15 +09:00
9 changed files with 355 additions and 96 deletions
@@ -0,0 +1,17 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
ElaprDmndResponse,
ElaprDmndSaveRequest,
ElaprDmndService,
} from "../model/elapr-dmnd-interface";
async function create(body: ElaprDmndSaveRequest): Promise<ElaprDmndResponse> {
const response = await client.post<ElaprDmndResponse>(ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, body);
return response.data;
}
export const ElaprDmndApi: ElaprDmndService = {
create,
};
@@ -0,0 +1,7 @@
import { client } from "@/shared/api";
export async function processElaprPrgrs(elaprPrgrsId: string, atrzPrgrsSttsComCd = "ATRZ_STTS_003"): Promise<void> {
await client.put(`/as-elapr-service/api/v1/elapr-prgrs-stts/${elaprPrgrsId}/process`, {
atrzPrgrsSttsComCd,
});
}
@@ -0,0 +1,22 @@
export interface ElaprDmndSaveRequest {
elaprDocNo: string;
elaprTmpltId: string;
dmndTtl: string;
dmndCn: string;
}
export interface ElaprDmndResponse {
elaprDmndId: string;
elaprDocNo: string;
elaprTmpltId: string;
dmndTtl: string;
dmndCn: string;
atrzSttsComCd: string;
rqstrUuid: string;
regDt: string;
updtDt: string;
}
export interface ElaprDmndService {
create(body: ElaprDmndSaveRequest): Promise<ElaprDmndResponse>;
}
+1
View File
@@ -8,4 +8,5 @@ export {
type InstlSncnMng,
type InstlSncnMngSearchParams,
type SpringPage,
type ElaprDocPrgrsClientResponseDto,
} from "./model/instl-sncn-interface";
@@ -22,6 +22,8 @@ export interface InstlSncnMngSearchParams {
}
export interface InstlSncnMng {
gugun: string;
sido: string;
instlSncnDocNo: string;
admdstCd: string;
sidoNm?: string;
@@ -108,6 +110,28 @@ export interface InstlSncnApplySaveRequestDto {
export type ReviewStepCode = "LOCAL" | "ENV" | "KECO";
/** 전자결재 진행상태 (클라이언트 응답용) */
export interface ElaprDocPrgrsClientResponseDto {
elaprDocNo?: string;
elaprDmndId?: string;
elaprPrgrsId?: string;
atrzSeqNo?: number;
atrzSeComCd?: string;
atrzSeComNm?: string;
aprvrUuid?: string;
aprvrUuidNm?: string;
atrzPrgrsSttsComCd?: string;
atrzPrgrsSttsComNm?: string;
rjctRsn?: string | null;
atrzDt?: string | null;
jbpsSeNm?: string;
jbgdSeNm?: string;
ognzNm?: string;
}
// det 응답에 전자결재 진행상태 목록을 포함
export interface InstlSncnMngDetResponse {
detDto: InstlSncnMng;
bizSclList: any[];
@@ -115,6 +139,7 @@ export interface InstlSncnMngDetResponse {
cklstResultList?: any[];
esntlFileList: any[];
etcFileList: any[];
elaprDocPrgrsList?: ElaprDocPrgrsClientResponseDto[] | null;
}
export interface InstlSncnMngService {
@@ -231,7 +231,7 @@ function CnfmListPage() {
<th>담당자</th>
<th>해당기관</th>
<th>진행상황</th>
<th>시행일</th>
<th>신청일</th>
</tr>
</thead>
<tbody>
@@ -253,12 +253,12 @@ function CnfmListPage() {
className="cnfm-row-clickable"
onClick={() => router.push(`/instl-sncn/${row.instlSncnDocNo}`)}
>
<td>{row.sidoNm || "-"}</td>
<td>{row.sggNm || "-"}</td>
<td>{row.sido || "-"}</td>
<td>{row.gugun|| "-"}</td>
<td className="text-left">{row.bizNm || "-"}</td>
<td>{row.taskSeCdNm || row.taskSeComCd || "-"}</td>
<td>{row.chrgrNm || row.aplcntNm || "-"}</td>
<td>{row.instNm || row.aplcntOgdpNm || "-"}</td>
<td>{row.aplcntNm || "-"}</td>
<td>{row.envoSeCdNm || "-"}</td>
<td>
<div className="cnfm-step-wrap">
<div className="cnfm-step-row">
@@ -155,12 +155,14 @@ const normalizeChecklistResultRows = (payload: any): any[] => {
*/
function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
const router = useRouter();
const userInfo2 = useUserStore((state) => state.userInfo);
const userInfo = useUserStore((state) => state.userInfo);
// userInfo?.dtlInstCd 테스트 코드 ENV.ROLE_USER03 , KECO 권한 테스트용 (실제 권한에 맞게 변경 필요)
/*
const userInfo = {
...userInfo2,
dtlInstCd: "ROLE_USER03", //ROLE_USER03, ROLE_USER02, ROLE_USER01 KECO 권한 테스트용 (실제 권한에 맞게 변경 필요)
};
*/
const alert = useAlertStore((state) => state.setMessage);
const qc = useQueryClient();
const currentDet = useMemo(() => initialData?.detDto ?? initialData, [initialData]);
@@ -204,7 +206,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
const [docSeComCd] = useState("INSTL");
const [taskSeComCdNm, setTaskSeComCdNm] = useState<string>(
() => (currentDet?.taskSeComCdNm as string) ?? "재이용수",
() => (currentDet?.taskSeComCdNm as string) ?? "",
);
const [taskSeComCd, setTaskSeComCd] = useState<string>(() => (currentDet?.taskSeComCd as string) ?? "RE");
@@ -212,7 +214,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
// do not override server-provided initialData (modify mode)
if (currentDet) return;
const raw = sessionStorage.getItem(INSTL_SNCN_REGISTER_SELECTION_KEY);
const raw = sessionStorage.getItem(INSTL_SNCN_REGISTER_SELECTION_KEY);
if (!raw) return;
try {
@@ -220,7 +222,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
taskSeComCdList?: string[];
taskSeComCdNmList?: string[];
};
setTaskSeComCd(parsed.taskSeComCdList?.join(",") || "RE");
setTaskSeComCdNm(parsed.taskSeComCdNmList?.join(",") || "재이용수");
} catch {
@@ -436,7 +438,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
// 수정모드인 경우 서버가 전달한 업무구분(단일 혹은 콤마로 결합된 복수)을 상태로 복원
setTaskSeComCd(det.taskSeComCd);
setTaskSeComCdNm(det.taskSeComCdNm );
setTaskSeComCdNm(det.taskSeCdNm );
// 매핑 완료 플래그 설정 — 이후 동일 컴포넌트 인스턴스에서 재매핑 방지
initialMappedRef.current = true;
@@ -634,7 +636,6 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
const { data: cklstTree = [] } = useQuery<CklstTreeNode[]>({
queryKey: ["cklstTree", docSeComCd, taskSeComCd],
queryFn: async () => {
console.log("Fetching checklist tree with", { docSeComCd, taskSeComCd });
// support comma-separated task codes (e.g. "RE,SP") by splitting
const raw = taskSeComCd ?? "";
const codes = raw
@@ -1125,7 +1126,7 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
}
const toInstlSncnMngPayload = (prgrsSttsComCd: "SV" | "ST"| "ER"): InstlSncnMng => ({
instlSncnDocNo: docId ?? "", // 수정 시 기존 문서번호 유지, 신규 저장 시 빈값
instlSncnDocNo: docId ?? "",
admdstCd: "",
sidoNm: "",
sggNm: "",
@@ -1156,6 +1157,8 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
regDt: "",
mdfcnDt: "",
rgtrNm: "",
gugun: "",
sido: ""
});
async function handleSave() {
@@ -1304,7 +1307,9 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
<th style={{ textAlign: "center" }}>신청 지자체</th>
<td>
<span className="inline-block min-w-[16rem] whitespace-nowrap text-[1.3rem] text-gray-500">
{userInfo?.enoNm ?? "\u00A0"}
{isModifyMode
? `${currentDet?.sido ?? "-"} ${currentDet?.gugun ?? "-"}`
: userInfo?.laeSeNm ?? "\u00A0"}
</span>
</td>
</tr>
@@ -1313,9 +1318,9 @@ function CnfmRegistPage({ initialData }: { initialData?: any } = {}) {
<td colSpan={3}>
<div className="flex gap-4 text-[1.3rem]">
{[
["CNFM", "설치인가"],
["APR", "설치승인"],
["AFA", "폐지인가"],
["1", "설치인가"],
["2", "설치승인"],
["3", "폐지승인"],
].map(([val, label]) => (
<label key={val} className="flex cursor-pointer items-center gap-1">
<input
@@ -11,8 +11,11 @@ import { useRouter } from "next/navigation";
import { PROXY_HOST } from "@/shared/config";
import { formatFileSize } from "@/shared/lib/file-utils";
import axios from "axios";
import { processElaprPrgrs } from "@/entities/elapr/api/elapr-prgrs-service";
import { ElaprDmndApi } from "@/entities/elapr/api/elapr-dmnd-service";
import { dayjs, useUserStore, useAlertStore } from "@/shared/lib";
import { InstlSncnApi, type InstlSncnApplySaveRequestDto } from "@/entities/instl-sncn";
import { client } from "@/shared/api";
import "./cnfm-page.css";
@@ -124,6 +127,134 @@ function CnfmViewPage({ id }: { id: string }) {
}, [cklstAll]);
const esntlFileList = data.esntlFileList ?? [];
const etcFileList = data.etcFileList ?? [];
const elaprDocPrgrsList = (data.elaprDocPrgrsList ?? []) as any[];
const renderApproverCell = (code: string) => {
const row = elaprDocPrgrsList.find((r) => r.atrzSeComCd === code);
if (!row) return <div className="text-center">-</div>;
const status = row.atrzPrgrsSttsComCd ?? "";
const statusMap: Record<string, { label: string; badgeClass: string; icon: string }> = {
ATRZ_PRGRS_001: { label: "대기", badgeClass: "bg-gray-100 text-gray-700", icon: "⏳" },
ATRZ_PRGRS_002: { label: "요청", badgeClass: "bg-yellow-100 text-yellow-800", icon: "📨" },
ATRZ_PRGRS_003: { label: "승인", badgeClass: "bg-green-100 text-green-800", icon: "✔️" },
ATRZ_PRGRS_004: { label: "반려", badgeClass: "bg-red-100 text-red-800", icon: "✖️" },
};
const st = statusMap[status] ?? { label: status || "", badgeClass: "bg-gray-100 text-gray-700", icon: "" };
return (
<div className="text-center">
<div className="font-semibold">{row.aprvrUuidNm ?? "-"}</div>
<div className="text-[0.95rem] text-gray-500">
{row.ognzNm ? `${row.ognzNm}` : ""}
{row.jbpsSeNm || row.jbgdSeNm ? ` · ${row.jbpsSeNm ?? ""}${row.jbgdSeNm ? ` ${row.jbgdSeNm}` : ""}` : ""}
</div>
<div className="text-[0.9rem] mt-1 flex items-center justify-center gap-2">
{row.atrzDt ? <span className="text-gray-400">{dayjs(row.atrzDt).format("YYYY-MM-DD HH:mm:ss")}</span> : null}
{st.label ? (
<span
title={`${row.atrzPrgrsSttsComNm ?? st.label}${row.rjctRsn ? ` — 사유: ${row.rjctRsn}` : ""}${row.atrzDt ? ` · ${dayjs(row.atrzDt).format("YYYY-MM-DD HH:mm:ss")}` : ""}`}
className={`inline-flex items-center px-2 py-0.5 rounded-md text-xs font-semibold ml-1 ${st.badgeClass}`}
>
<span className="mr-1">{st.icon}</span>
<span>{st.label}</span>
</span>
) : null}
</div>
</div>
);
};
const orderedApprovers = useMemo(() => {
return (elaprDocPrgrsList ?? []).slice().sort((a: any, b: any) => {
const A = Number(a?.atrzSeqNo ?? 0);
const B = Number(b?.atrzSeqNo ?? 0);
return A - B;
});
}, [elaprDocPrgrsList]);
const handleApproveClick = async (elaprPrgrsId: any, atrzSeComCd: any) => {
if (!elaprPrgrsId) {
alert("결재 정보가 올바르지 않습니다.");
return;
}
try {
await processElaprPrgrs(String(elaprPrgrsId), "ATRZ_PRGRS_003"); //진행상태를 승인으로 업데이트
alert("결재가 완료되었습니다.");
try {
await query.refetch();
} catch (e) {
/* ignore refetch errors */
}
} catch (err: any) {
console.error("결재 처리 실패", err);
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : "결재 처리 중 오류가 발생했습니다.";
alert(msg);
}
};
const handleAssignApproverClick = async (elaprTmpltId: string) => {
try {
const payload = {
elaprDocNo: det.instlSncnDocNo,
elaprTmpltId: elaprTmpltId, /* 팝업에서 선택된 전자결제 템플릿 */
dmndTtl: det.bizNm, /* "구례 하수도 처리시설 2026", */
dmndCn: det.bizCn /*"구례 발주 하수도 처리시설 사업 2026년도 진행분 결제 요청 입니다."*/
};
// ElaprDmndApi 서비스를 통해 백엔드 연동
await ElaprDmndApi.create(payload);
alert("결재자 지정(결재요청 등록)이 완료되었습니다.");
try {
await query.refetch();
} catch (e) {
/* ignore refetch errors */
}
} catch (err: any) {
console.error("결재자 지정 실패", err);
const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : "결재자 지정 중 오류가 발생했습니다.";
alert(msg);
}
};
const renderApproverRow = (row: any) => {
if (!row) return <div className="text-center">-</div>;
const status = row.atrzPrgrsSttsComCd ?? "";
const statusMap: Record<string, { label: string; badgeClass: string; icon: string }> = {
ATRZ_PRGRS_001: { label: "대기", badgeClass: "bg-gray-100 text-gray-700", icon: "⏳" },
ATRZ_PRGRS_002: { label: "요청", badgeClass: "bg-yellow-100 text-yellow-800", icon: "📨" },
ATRZ_PRGRS_003: { label: "승인", badgeClass: "bg-green-100 text-green-800", icon: "✔️" },
ATRZ_PRGRS_004: { label: "반려", badgeClass: "bg-red-100 text-red-800", icon: "✖️" },
};
const st = statusMap[status] ?? { label: status || "", badgeClass: "bg-gray-100 text-gray-700", icon: "" };
return (
<div className="text-center">
<div className="font-semibold">{row.aprvrUuidNm ?? "-"}</div>
<div className="text-[0.95rem] text-gray-500">
{row.ognzNm ? `${row.ognzNm}` : ""}
{row.jbpsSeNm || row.jbgdSeNm ? ` · ${row.jbpsSeNm ?? ""}${row.jbgdSeNm ? ` ${row.jbgdSeNm}` : ""}` : ""}
</div>
<div className="text-[0.9rem] mt-1 flex items-center justify-center gap-2">
{row.atrzDt ? <span className="text-gray-400">{dayjs(row.atrzDt).format("YYYY-MM-DD HH:mm:ss")}</span> : null}
{st.label ? (
<>
<span
title={`${row.atrzPrgrsSttsComNm ?? st.label}${row.rjctRsn ? ` — 사유: ${row.rjctRsn}` : ""}${row.atrzDt ? ` · ${dayjs(row.atrzDt).format("YYYY-MM-DD HH:mm:ss")}` : ""}`}
className={`inline-flex items-center px-2 py-0.5 rounded-md text-xs font-semibold ml-1 ${st.badgeClass}`}
>
<span className="mr-1">{st.icon}</span>
<span>{st.label}</span>
</span>
{status === "ATRZ_PRGRS_002" && userInfo?.userUnqId === row.aprvrUuid ? (
<button
type="button"
onClick={() => handleApproveClick(row.elaprPrgrsId, row.atrzSeComCd)}
className="ml-2 px-2 py-1 bg-blue-500 text-white text-xs rounded"
>
결제
</button>
) : null}
</>
) : null}
</div>
</div>
);
};
const currentPrgrsSttsComCd = (det.prgrsSttsComCd as string | undefined) ?? "ST";
const roleBasedMaxStepCount = getRoleStepCount(userInfo?.dtlInstCd);
const progressBasedMaxStepCount = getProgressStepCount(currentPrgrsSttsComCd);
@@ -267,6 +398,58 @@ function CnfmViewPage({ id }: { id: string }) {
return (
<div className="mt-6 space-y-4">
{/* orderedApprovers.length > 0 && ( */}
<div className="cnfm-card p-4">
<h3 className="text-[1.5rem] font-semibold">결재 정보</h3>
<div className="cnfm-table-wrap mt-3">
<table className="cnfm-table w-full">
<thead>
<tr>
{orderedApprovers.length === 0 ? (
<>
<th className="text-center">접수자</th>
<th className="text-center">검토자</th>
<th className="text-center">승인자</th>
<th className="text-center">최종승인자</th>
</>
) : (
orderedApprovers.map((r: any) => (
<th key={String(r.elaprPrgrsId ?? r.atrzSeComCd)} className="text-center">
{r.atrzSeComNm ?? r.atrzSeComCd}
</th>
))
)}
</tr>
</thead>
<tbody>
<tr>
{orderedApprovers.length === 0 ? (
<>
<td>{renderApproverCell("ATRZ01")}</td>
<td>{renderApproverCell("ATRZ02")}</td>
<td>{renderApproverCell("ATRZ03")}</td>
<td>{renderApproverCell("ATRZ99")}</td>
</>
) : (
orderedApprovers.map((r: any) => (
<td key={String(r.elaprPrgrsId ?? r.atrzSeComCd)}>{renderApproverRow(r)}</td>
))
)}
</tr>
</tbody>
</table>
{/** 결제자 지정 버튼 */}
<div className="mt-4 text-right">
<Button
type="button"
onClick={() => handleAssignApproverClick("TMPLT20260611000001")}
>
결재자 지정
</Button>
</div>
</div>
</div>
{/* )} */}
<div className="cnfm-card p-4">
<h2 className="cnfm-section-title">설치인가 상세보기</h2>
<p className="mt-2 text-[1.3rem] text-gray-600">문서번호: <strong>{det.instlSncnDocNo}</strong></p>
@@ -279,30 +462,30 @@ function CnfmViewPage({ id }: { id: string }) {
<tr>
<th>사업명</th>
<td>{det.bizNm || "-"}</td>
<th>사업기간</th>
<td>{(det.bizBgngYr || "-") + " ~ " + (det.bizEndYr || "-")}</td>
<th>사업기간</th>
<td>{(det.bizBgngYr || "-") + " ~ " + (det.bizEndYr || "-")}</td>
</tr>
<tr>
<th>사업내용</th>
<td colSpan={3}>{det.bizCn || "-"}</td>
</tr>
<tr>
<th>업무구분</th>
<th>구분</th>
<td>{det.taskSeCdNm || det.taskSeComCd || "-"}</td>
<th>행정동ID</th>
<td>{det.admdstCd || "-"}</td>
<th>신청지자체</th>
<td>{det.sido || "-"} {det.gugun || "-"}</td>
</tr>
<tr>
<th>사업구분(인가구분)</th>
<td colSpan={3}>{det.sncnSeComCd || "-"}</td>
<th>사업구분</th>
<td colSpan={3}>{det.sncnSeCdNm || "-"}</td>
</tr>
<tr>
<th>진행상태</th>
<td>{det.prgrsSttsCdNm || det.prgrsSttsComCd || "-"}</td>
<td>{det.prgrsSttsCdNm || det.prgrsSttsComCd || "-"}</td>
<th>예산사업코드</th>
<td>{det.bgtBizCd || "-"}</td>
</tr>
</tbody>
</table>
</div>
@@ -423,7 +606,7 @@ function CnfmViewPage({ id }: { id: string }) {
</thead>
<tbody>
{bizCstList.map((node: any) => {
const pl = indentPx(node.indAcntgSeCdNm);
const bscPlanAmt = Number(node.bscPlanAmt) || 0;
const budgetReflct = Number(node.bgtRfltAmt) || 0;
@@ -438,8 +621,8 @@ function CnfmViewPage({ id }: { id: string }) {
node.treeLevel === 1
? "bg-blue-50 font-bold"
: node.treeLevel === 2
? "bg-gray-100 font-semibold"
: ""
? "bg-gray-100 font-semibold"
: ""
}
>
{node.treeLevel <= 2 ? (
@@ -481,31 +664,30 @@ function CnfmViewPage({ id }: { id: string }) {
</div>
{/* 체크리스트 */}
<div className="cnfm-card p-4">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-[1.5rem] font-bold text-gray-700">체크리스트</h3>
<div className="flex items-center gap-4">
<div className="flex gap-2 items-center">
{Array.from(new Set(taskCodes)).map((code) => {
const name = TASK_CODE_LABELS[code] ?? code;
const active = selectedTaskCode === code;
return (
<button
key={code}
type="button"
className={`rounded border px-3 py-1 text-[1.2rem] font-semibold ${
active ? "border-blue-500 bg-blue-50 text-blue-700" : "border-gray-300 bg-white text-gray-600"
<div className="cnfm-card p-4">
<div className="mb-3 flex items-center justify-between">
<h3 className="text-[1.5rem] font-bold text-gray-700">체크리스트</h3>
<div className="flex items-center gap-4">
<div className="flex gap-2 items-center">
{Array.from(new Set(taskCodes)).map((code) => {
const name = TASK_CODE_LABELS[code] ?? code;
const active = selectedTaskCode === code;
return (
<button
key={code}
type="button"
className={`rounded border px-3 py-1 text-[1.2rem] font-semibold ${active ? "border-blue-500 bg-blue-50 text-blue-700" : "border-gray-300 bg-white text-gray-600"
}`}
onClick={() => setSelectedTaskCode(code)}
>
{name}
</button>
);
})}
</div>
<div className="text-[1.1rem] text-gray-500">{`(${cklstStats.completed}/${cklstStats.total})`}</div>
onClick={() => setSelectedTaskCode(code)}
>
{name}
</button>
);
})}
</div>
<div className="text-[1.1rem] text-gray-500">{`(${cklstStats.completed}/${cklstStats.total})`}</div>
</div>
</div>
<div className="mb-3 flex flex-wrap gap-2">
{visibleReviewStepCodes.length === 0 ? (
<span className="text-[1.2rem] text-gray-500">
@@ -516,11 +698,10 @@ function CnfmViewPage({ id }: { id: string }) {
<button
key={stepCode}
type="button"
className={`rounded border px-3 py-1 text-[1.2rem] font-semibold ${
selectedReviewStep === stepCode
? "border-blue-500 bg-blue-50 text-blue-700"
: "border-gray-300 bg-white text-gray-600"
}`}
className={`rounded border px-3 py-1 text-[1.2rem] font-semibold ${selectedReviewStep === stepCode
? "border-blue-500 bg-blue-50 text-blue-700"
: "border-gray-300 bg-white text-gray-600"
}`}
onClick={() => setSelectedReviewStep(stepCode)}
>
{stepCode}
@@ -551,7 +732,7 @@ function CnfmViewPage({ id }: { id: string }) {
</button>
</th>
<th className="w-[9rem]">
<button
<button
type="button"
>
아니오
@@ -568,50 +749,50 @@ function CnfmViewPage({ id }: { id: string }) {
selectedChecklistRows.map((row: any) => {
const key = `${row.taskSeComCd ?? ""}_${row.cklstSn}_${row.cklstArtclSn ?? ""}_${row.cklstRvwSn}_${row.rvwStpComCd ?? "LOCAL"}`;
const hasResult = row.rvwRsltAnmlyYn === "Y" || row.rvwRsltAnmlyYn === "N";
const showRadios = row.bchckUseYn === "Y" ;
const showRadios = row.bchckUseYn === "Y";
return (
<tr key={key}>
<td className="text-center text-[1.2rem]">{row.cklstArtclNm}</td>
<td className="!text-left pl-2 text-[1.2rem] text-gray-600" style={{ paddingLeft: `${Math.max(0, row.treeLevel - 1) * 16 + 8}px` }}>{row.treeLevel > 1 ? "└ " : ""}{row.rvwCn}</td>
{!showRadios ? (
<>
<td className="text-center">-</td>
<td className="text-center">-</td>
</>
) : (
<>
<td className="text-center">
<input
type="radio"
name={`chk_${key}`}
value="Y"
checked={(viewCheckResults[key] ?? "") === "Y"}
disabled={selectedReviewStep === "LOCAL"}
onChange={() => setViewCheckResults((prev) => ({ ...prev, [key]: "Y" }))}
/>
</td>
<td className="text-center">
<input
type="radio"
name={`chk_${key}`}
value="N"
checked={(viewCheckResults[key] ?? "") === "N"}
disabled={selectedReviewStep === "LOCAL"}
onChange={() => setViewCheckResults((prev) => ({ ...prev, [key]: "N" }))}
/>
</td>
</>
)}
{showRadios && selectedReviewStep !== "LOCAL" ? (
<td className="text-left">
<textarea
className="cnfm-input w-full"
value={viewCheckComments[key] ?? ""}
onChange={(e) => setViewCheckComments((prev) => ({ ...prev, [key]: e.target.value }))}
/>
</td>
) : null}
{!showRadios ? (
<>
<td className="text-center">-</td>
<td className="text-center">-</td>
</>
) : (
<>
<td className="text-center">
<input
type="radio"
name={`chk_${key}`}
value="Y"
checked={(viewCheckResults[key] ?? "") === "Y"}
disabled={selectedReviewStep === "LOCAL"}
onChange={() => setViewCheckResults((prev) => ({ ...prev, [key]: "Y" }))}
/>
</td>
<td className="text-center">
<input
type="radio"
name={`chk_${key}`}
value="N"
checked={(viewCheckResults[key] ?? "") === "N"}
disabled={selectedReviewStep === "LOCAL"}
onChange={() => setViewCheckResults((prev) => ({ ...prev, [key]: "N" }))}
/>
</td>
</>
)}
{showRadios && selectedReviewStep !== "LOCAL" ? (
<td className="text-left">
<textarea
className="cnfm-input w-full"
value={viewCheckComments[key] ?? ""}
onChange={(e) => setViewCheckComments((prev) => ({ ...prev, [key]: e.target.value }))}
/>
</td>
) : null}
</tr>
);
})
@@ -628,7 +809,7 @@ function CnfmViewPage({ id }: { id: string }) {
<table className="cnfm-table">
<thead>
<tr>
<th>필수 자료명</th>
<th>필수 자료명</th>
<th>파일명</th>
<th className="w-[8rem] text-right">크기</th>
</tr>
+1
View File
@@ -20,6 +20,7 @@ export const ENDPOINT = {
AS_ELAPR_SERVICE: {
ELAPR_TMPLT: "/as-elapr-service/api/v1/elapr-tmplt",
ELAPR_APLN: "/as-elapr-service/api/v1/elapr-apln",
ELAPR_DMND: "/as-elapr-service/api/v1/elapr-dmnd",
},
AS_CHECKLIST_SERVICE: {
CKLST: "/as-service/api/v1/cklst",