[st frontend] 상세 페이지 수정 작업

This commit is contained in:
2026-05-29 11:19:49 +09:00
parent e98fe69d68
commit 68ac1168e9
2 changed files with 222 additions and 26 deletions
@@ -1220,7 +1220,7 @@ function CnfmRegistPage() {
{/* ── 필수 첨부파일 ── */}
<div className="cnfm-card p-4">
<h3 className="mb-3 border-b pb-2 text-[1.5rem] font-bold text-gray-700">첨부파일</h3>
<h3 className="mb-3 border-b pb-2 text-[1.5rem] font-bold text-gray-700">필수 첨부파일</h3>
<div className="cnfm-table-wrap">
<table className="cnfm-table">
<thead>
@@ -1,11 +1,14 @@
"use client";
/* eslint-disable @typescript-eslint/no-explicit-any, import/order */
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { Button } from "@common/components";
import { InstlSncnApi } from "@/entities/instl-sncn";
import { dayjs } from "@/shared/lib";
import { PROXY_HOST } from "@/shared/config";
import "./cnfm-page.css";
@@ -25,15 +28,30 @@ function CnfmViewPage({ id }: { id: string }) {
return <div className="mt-6 text-[1.4rem]">상세 정보를 찾을 수 없습니다.</div>;
}
const data = query.data;
const data = query.data as any;
// support API shape where payload includes detDto + lists
const det = data.detDto ?? data;
const bizSclList = data.bizSclList ?? [];
const bizCstList = data.bizCstList ?? [];
const cklstResultList = data.cklstResultList ?? [];
const esntlFileList = data.esntlFileList ?? [];
const etcFileList = data.etcFileList ?? [];
const fmt = (v: number | null | undefined) =>
typeof v === "number" ? v.toLocaleString() : v === null || v === undefined ? "" : String(v);
const indentPx = (indNm: string | undefined) => {
if (!indNm) return 0;
const spaces = indNm.length - indNm.trimStart().length;
return (spaces / 4) * 16;
};
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">
문서번호: <strong>{data.instlSncnDocNo}</strong>
</p>
<p className="mt-2 text-[1.3rem] text-gray-600">문서번호: <strong>{det.instlSncnDocNo}</strong></p>
</div>
<div className="cnfm-card p-4">
@@ -42,27 +60,25 @@ function CnfmViewPage({ id }: { id: string }) {
<tbody>
<tr>
<th>사업명</th>
<td>{data.bizNm || "-"}</td>
<td>{det.bizNm || "-"}</td>
<th>업무구분</th>
<td>{data.taskSeCdNm || data.taskSeComCd || "-"}</td>
<td>{det.taskSeCdNm || det.taskSeComCd || "-"}</td>
</tr>
<tr>
<th>사업기간</th>
<td>
{(data.bizBgngYr || "-") + " ~ " + (data.bizEndYr || "-")}
</td>
<td>{(det.bizBgngYr || "-") + " ~ " + (det.bizEndYr || "-")}</td>
<th>진행상태</th>
<td>{data.prgrsSttsCdNm || data.prgrsSttsComCd || "-"}</td>
<td>{det.prgrsSttsCdNm || det.prgrsSttsComCd || "-"}</td>
</tr>
<tr>
<th>행정동ID</th>
<td>{data.dongId || "-"}</td>
<td>{det.dongId || "-"}</td>
<th>예산사업코드</th>
<td>{data.bgtBizCd || "-"}</td>
<td>{det.bgtBizCd || "-"}</td>
</tr>
<tr>
<th>사업내용</th>
<td colSpan={3}>{data.bizCn || "-"}</td>
<td colSpan={3}>{det.bizCn || "-"}</td>
</tr>
</tbody>
</table>
@@ -74,27 +90,27 @@ function CnfmViewPage({ id }: { id: string }) {
<tbody>
<tr>
<th>신청자명</th>
<td>{data.aplcntNm || "-"}</td>
<td>{det.aplcntNm || "-"}</td>
<th>신청자 연락처</th>
<td>{data.aplcntMblTelno || "-"}</td>
<td>{det.aplcntMblTelno || "-"}</td>
</tr>
<tr>
<th>신청자 소속</th>
<td>{data.aplcntOgdpNm || "-"}</td>
<td>{det.aplcntOgdpNm || "-"}</td>
<th>신청자 이메일</th>
<td>{data.aplcntEmlAddr || "-"}</td>
<td>{det.aplcntEmlAddr || "-"}</td>
</tr>
<tr>
<th>설계자명</th>
<td>{data.dsgnrNm || "-"}</td>
<td>{det.dsgnrNm || "-"}</td>
<th>설계자 연락처</th>
<td>{data.dsgnrTelno || "-"}</td>
<td>{det.dsgnrTelno || "-"}</td>
</tr>
<tr>
<th>설계자 소속</th>
<td>{data.dsgnrOgdpNm || "-"}</td>
<td>{det.dsgnrOgdpNm || "-"}</td>
<th>설계자 이메일</th>
<td>{data.dsgnrEmlAddr || "-"}</td>
<td>{det.dsgnrEmlAddr || "-"}</td>
</tr>
</tbody>
</table>
@@ -106,20 +122,200 @@ function CnfmViewPage({ id }: { id: string }) {
<tbody>
<tr>
<th>등록일시</th>
<td>{data.regDt ? dayjs(data.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}</td>
<td>{det.regDt ? dayjs(det.regDt).format("YYYY-MM-DD HH:mm:ss") : "-"}</td>
<th>수정일시</th>
<td>{data.mdfcnDt ? dayjs(data.mdfcnDt).format("YYYY-MM-DD HH:mm:ss") : "-"}</td>
<td>{det.mdfcnDt ? dayjs(det.mdfcnDt).format("YYYY-MM-DD HH:mm:ss") : "-"}</td>
</tr>
<tr>
<th>등록자명</th>
<td>{data.rgtrNm || "-"}</td>
<td>{det.rgtrNm || "-"}</td>
<th>첨부파일그룹ID</th>
<td>{data.atchFileGroupId || "-"}</td>
<td>{det.atchFileGroupId || "-"}</td>
</tr>
</tbody>
</table>
</div>
{/* 사업규모 */}
<div className="cnfm-card p-4">
<h3 className="mb-3 border-b pb-2 text-[1.5rem] font-bold text-gray-700">사업규모</h3>
<div className="cnfm-table-wrap">
<table className="cnfm-table">
<thead>
<tr>
<th className="w-[30rem]">구분</th>
<th>기본계획(A)</th>
<th>예산반영(B)</th>
<th>금회설계시(C)</th>
<th>증감(C-B)</th>
</tr>
</thead>
<tbody>
{bizSclList.length === 0 ? (
<tr>
<td colSpan={5} className="cnfm-td-empty">조회된 사업규모 항목이 없습니다.</td>
</tr>
) : (
bizSclList.map((node: any) => {
const pl = indentPx(node.indCnstpSeCdNm);
const a = Number(node.bscPlanQty) || 0;
const b = Number(node.bgtRfltQty) || 0;
const c = Number(node.tstmDesignQty) || 0;
const diff = c - b;
return (
<tr key={node.cnstpSeCd} className={node.treeLevel === 1 ? "bg-gray-50 font-semibold" : ""}>
<td className="bg-gray-50 pl-2 text-left" style={{ paddingLeft: `${pl + 8}px` }}>{node.cnstpSeCdNm}</td>
<td><div className="text-right">{fmt(a)}</div></td>
<td><div className="text-right">{fmt(b)}</div></td>
<td><div className="text-right">{fmt(c)}</div></td>
<td><div className="text-right">{diff > 0 ? `+${fmt(diff)}` : fmt(diff)}</div></td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</div>
{/* 총사업비 */}
<div className="cnfm-card p-4">
<h3 className="mb-3 border-b pb-2 text-[1.5rem] font-bold text-gray-700">총사업비 <span className="text-[1.2rem] font-normal text-gray-400">(단위: 백만원)</span></h3>
<div className="cnfm-table-wrap">
<table className="cnfm-table w-full table-fixed">
<thead>
<tr>
<th colSpan={2} className="w-[30rem]">구분</th>
<th>예산반영(A)</th>
<th>표준사업비(B)</th>
<th>금회설계시(C)</th>
<th>증감</th>
<th>%</th>
</tr>
</thead>
<tbody>
{bizCstList.map((node: any) => {
const pl = indentPx(node.indAcntgSeCdNm);
const a = Number(node.bscPlanAmt) || 0;
const b = Number(node.bgtRfltAmt) || 0;
const c = Number(node.tstmDesignAmt) || 0;
const diff = c - b;
return (
<tr key={node.acntgSeCd} className={node.treeLevel === 1 ? "bg-blue-50 font-bold" : node.treeLevel === 2 ? "bg-gray-100 font-semibold" : ""}>
{node.treeLevel <= 2 ? (
<td colSpan={2} className="pl-2 text-left">{node.acntgSeCdNm}</td>
) : (
<>
<td className="w-[4rem]"></td>
<td className="bg-gray-50 text-left" style={{ paddingLeft: `${pl + 8}px` }}>{node.acntgSeCdNm}</td>
</>
)}
<td><div className="text-right">{fmt(a)}</div></td>
<td><div className="text-right">{fmt(b)}</div></td>
<td><div className="text-right">{fmt(c)}</div></td>
<td><div className="text-right">{diff > 0 ? `+${fmt(diff)}` : fmt(diff)}</div></td>
<td><div className="text-right">{b !== 0 ? `${Math.round((diff / b) * 100)}%` : "-"}</div></td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
{/* 체크리스트 */}
<div className="cnfm-card p-4">
<h3 className="text-[1.5rem] font-bold text-gray-700">체크리스트</h3>
<div className="cnfm-table-wrap mt-3">
<table className="cnfm-table">
<thead>
<tr>
<th className="w-[20rem]">검토항목</th>
<th>주요 검토내용</th>
<th>검토 결과</th>
</tr>
</thead>
<tbody>
{cklstResultList.length === 0 ? (
<tr>
<td colSpan={3} className="cnfm-td-empty">조회된 체크리스트 항목이 없습니다.</td>
</tr>
) : (
cklstResultList.map((row: any) => (
<tr key={`${row.cklstSn}_${row.cklstRvwSn}`}>
<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>
<td className="text-center text-[1.2rem]">{row.bchckUseYn === "Y" ? (row.rvwRsltAnmlyYn === "Y" ? "예" : row.rvwRsltAnmlyYn === "N" ? "아니오" : "-") : "-"}</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
{/* 첨부파일 */}
<div className="cnfm-card p-4">
<h3 className="mb-3 border-b pb-2 text-[1.5rem] font-bold text-gray-700">필수 첨부파일</h3>
<div className="cnfm-table-wrap">
<table className="cnfm-table">
<thead>
<tr>
<th>필수 자료명</th>
<th>파일형식</th>
<th>파일</th>
</tr>
</thead>
<tbody>
{esntlFileList.length === 0 ? (
<tr>
<td colSpan={3} className="cnfm-td-empty">조회된 필수 첨부파일 항목이 없습니다.</td>
</tr>
) : (
esntlFileList.map((f: any) => (
<tr key={f.esntlAtchFileId}>
<td className="pl-2">
<div className="w-full text-left">
{f.esntlAtchFileNm}
{f.esntlAtchYn === "Y" ? <span className="ml-1 text-red-500">*</span> : null}
</div>
</td>
<td>{(f.esntlAtchFileRmrk || "").replaceAll(",", "/") || "-"}</td>
<td>
{f.atflCd ? (
<a className="text-blue-600" href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${f.atflCd}`} target="_blank" rel="noreferrer">{f.orgnlFileNm || "다운로드"}</a>
) : (
<span className="text-gray-500">첨부없음</span>
)}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
<h4 className="mt-4 mb-2 text-[1.25rem] font-semibold">기타 첨부파일</h4>
{etcFileList.length === 0 ? (
<div className="text-gray-500">등록된 기타첨부파일이 없습니다.</div>
) : (
<ul className="space-y-2">
{etcFileList.map((f: any) => (
<li key={f.atflId}>
{f.orgnlFileNm ? (
<a className="text-blue-600" href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${f.atflCd}`} target="_blank" rel="noreferrer">{f.orgnlFileNm}</a>
) : (
<span className="text-gray-700">{f.physFileNm || f.atflId}</span>
)}
<span className="ml-2 text-gray-500">{f.atflFlieSz ? `(${Math.round(f.atflFlieSz / 1024)}KB)` : ""}</span>
</li>
))}
</ul>
)}
</div>
<div className="cnfm-action-row">
<Button color="white" onClick={() => router.push("/instl-sncn")}>목록</Button>
<Button onClick={() => router.push(`/instl-sncn/${id}/modify`)}>수정</Button>