This commit is contained in:
@@ -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>;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user